@formo/analytics 1.35.1 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +90 -240
  2. package/dist/cjs/src/FormoAnalytics.js +432 -1840
  3. package/dist/cjs/src/constants/base.d.ts +0 -1
  4. package/dist/cjs/src/constants/base.js +1 -3
  5. package/dist/cjs/src/event/EventFactory.js +1 -1
  6. package/dist/cjs/src/event/EventManager.d.ts +3 -1
  7. package/dist/cjs/src/event/EventManager.js +5 -1
  8. package/dist/cjs/src/event/type.d.ts +1 -0
  9. package/dist/cjs/src/evm/EvmEventTracker.d.ts +175 -0
  10. package/dist/cjs/src/evm/EvmEventTracker.js +1030 -0
  11. package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +132 -0
  12. package/dist/cjs/src/evm/EvmProviderRegistry.js +348 -0
  13. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +120 -0
  14. package/dist/cjs/src/evm/EvmRequestTracker.js +756 -0
  15. package/dist/cjs/src/queue/EventQueue.d.ts +28 -0
  16. package/dist/cjs/src/queue/EventQueue.js +97 -19
  17. package/dist/cjs/src/queue/type.d.ts +1 -0
  18. package/dist/cjs/src/tracking/TrackingPolicy.d.ts +146 -0
  19. package/dist/cjs/src/tracking/TrackingPolicy.js +200 -0
  20. package/dist/cjs/src/utils/mergeDeepRight.d.ts +22 -0
  21. package/dist/cjs/src/utils/mergeDeepRight.js +54 -0
  22. package/dist/cjs/src/validators/address.d.ts +1 -1
  23. package/dist/cjs/src/version.d.ts +1 -1
  24. package/dist/cjs/src/version.js +1 -1
  25. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +193 -1
  26. package/dist/cjs/src/wagmi/WagmiEventHandler.js +1317 -77
  27. package/dist/cjs/src/wagmi/types.d.ts +6 -0
  28. package/dist/cjs/src/wallet/WalletStateStore.d.ts +223 -0
  29. package/dist/cjs/src/wallet/WalletStateStore.js +515 -0
  30. package/dist/esm/src/FormoAnalytics.d.ts +90 -240
  31. package/dist/esm/src/FormoAnalytics.js +433 -1841
  32. package/dist/esm/src/constants/base.d.ts +0 -1
  33. package/dist/esm/src/constants/base.js +0 -2
  34. package/dist/esm/src/event/EventFactory.js +1 -1
  35. package/dist/esm/src/event/EventManager.d.ts +3 -1
  36. package/dist/esm/src/event/EventManager.js +5 -1
  37. package/dist/esm/src/event/type.d.ts +1 -0
  38. package/dist/esm/src/evm/EvmEventTracker.d.ts +175 -0
  39. package/dist/esm/src/evm/EvmEventTracker.js +1027 -0
  40. package/dist/esm/src/evm/EvmProviderRegistry.d.ts +132 -0
  41. package/dist/esm/src/evm/EvmProviderRegistry.js +345 -0
  42. package/dist/esm/src/evm/EvmRequestTracker.d.ts +120 -0
  43. package/dist/esm/src/evm/EvmRequestTracker.js +753 -0
  44. package/dist/esm/src/queue/EventQueue.d.ts +28 -0
  45. package/dist/esm/src/queue/EventQueue.js +97 -19
  46. package/dist/esm/src/queue/type.d.ts +1 -0
  47. package/dist/esm/src/tracking/TrackingPolicy.d.ts +146 -0
  48. package/dist/esm/src/tracking/TrackingPolicy.js +197 -0
  49. package/dist/esm/src/utils/mergeDeepRight.d.ts +22 -0
  50. package/dist/esm/src/utils/mergeDeepRight.js +51 -0
  51. package/dist/esm/src/version.d.ts +1 -1
  52. package/dist/esm/src/version.js +1 -1
  53. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +193 -1
  54. package/dist/esm/src/wagmi/WagmiEventHandler.js +1315 -76
  55. package/dist/esm/src/wagmi/types.d.ts +6 -0
  56. package/dist/esm/src/wallet/WalletStateStore.d.ts +223 -0
  57. package/dist/esm/src/wallet/WalletStateStore.js +512 -0
  58. package/dist/index.umd.min.js +1 -1
  59. package/package.json +5 -13
  60. package/dist/cjs/src/ramda/internal/_curry1.d.ts +0 -10
  61. package/dist/cjs/src/ramda/internal/_curry1.js +0 -26
  62. package/dist/cjs/src/ramda/internal/_curry2.d.ts +0 -10
  63. package/dist/cjs/src/ramda/internal/_curry2.js +0 -43
  64. package/dist/cjs/src/ramda/internal/_curry3.d.ts +0 -10
  65. package/dist/cjs/src/ramda/internal/_curry3.js +0 -74
  66. package/dist/cjs/src/ramda/internal/_has.d.ts +0 -2
  67. package/dist/cjs/src/ramda/internal/_has.js +0 -7
  68. package/dist/cjs/src/ramda/internal/_isObject.d.ts +0 -2
  69. package/dist/cjs/src/ramda/internal/_isObject.js +0 -7
  70. package/dist/cjs/src/ramda/internal/_isPlaceholder.d.ts +0 -2
  71. package/dist/cjs/src/ramda/internal/_isPlaceholder.js +0 -7
  72. package/dist/cjs/src/ramda/mergeDeepRight.d.ts +0 -3
  73. package/dist/cjs/src/ramda/mergeDeepRight.js +0 -14
  74. package/dist/cjs/src/ramda/mergeDeepWithKey.d.ts +0 -3
  75. package/dist/cjs/src/ramda/mergeDeepWithKey.js +0 -20
  76. package/dist/cjs/src/ramda/mergeWithKey.d.ts +0 -3
  77. package/dist/cjs/src/ramda/mergeWithKey.js +0 -26
  78. package/dist/esm/src/ramda/internal/_curry1.d.ts +0 -10
  79. package/dist/esm/src/ramda/internal/_curry1.js +0 -20
  80. package/dist/esm/src/ramda/internal/_curry2.d.ts +0 -10
  81. package/dist/esm/src/ramda/internal/_curry2.js +0 -37
  82. package/dist/esm/src/ramda/internal/_curry3.d.ts +0 -10
  83. package/dist/esm/src/ramda/internal/_curry3.js +0 -68
  84. package/dist/esm/src/ramda/internal/_has.d.ts +0 -2
  85. package/dist/esm/src/ramda/internal/_has.js +0 -4
  86. package/dist/esm/src/ramda/internal/_isObject.d.ts +0 -2
  87. package/dist/esm/src/ramda/internal/_isObject.js +0 -4
  88. package/dist/esm/src/ramda/internal/_isPlaceholder.d.ts +0 -2
  89. package/dist/esm/src/ramda/internal/_isPlaceholder.js +0 -4
  90. package/dist/esm/src/ramda/mergeDeepRight.d.ts +0 -3
  91. package/dist/esm/src/ramda/mergeDeepRight.js +0 -9
  92. package/dist/esm/src/ramda/mergeDeepWithKey.d.ts +0 -3
  93. package/dist/esm/src/ramda/mergeDeepWithKey.js +0 -15
  94. package/dist/esm/src/ramda/mergeWithKey.d.ts +0 -3
  95. package/dist/esm/src/ramda/mergeWithKey.js +0 -21
@@ -46,18 +46,8 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
46
46
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
47
  }
48
48
  };
49
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
50
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
51
- if (ar || !(i in from)) {
52
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
53
- ar[i] = from[i];
54
- }
55
- }
56
- return to.concat(ar || Array.prototype.slice.call(from));
57
- };
58
49
  Object.defineProperty(exports, "__esModule", { value: true });
59
50
  exports.FormoAnalytics = void 0;
60
- var mipd_1 = require("mipd");
61
51
  var constants_1 = require("./constants");
62
52
  var storage_1 = require("./storage");
63
53
  var cookiePolicy_1 = require("./storage/cookiePolicy");
@@ -65,12 +55,13 @@ var event_1 = require("./event");
65
55
  var queue_1 = require("./queue");
66
56
  var logger_1 = require("./logger");
67
57
  var consent_1 = require("./consent");
68
- var provider_1 = require("./provider");
69
58
  var session_1 = require("./session");
70
- var types_1 = require("./types");
71
59
  var address_1 = require("./utils/address");
72
- var timezone_1 = require("./utils/timezone");
73
- var validators_1 = require("./validators");
60
+ var TrackingPolicy_1 = require("./tracking/TrackingPolicy");
61
+ var WalletStateStore_1 = require("./wallet/WalletStateStore");
62
+ var EvmProviderRegistry_1 = require("./evm/EvmProviderRegistry");
63
+ var EvmEventTracker_1 = require("./evm/EvmEventTracker");
64
+ var EvmRequestTracker_1 = require("./evm/EvmRequestTracker");
74
65
  var chain_1 = require("./utils/chain");
75
66
  var wagmi_1 = require("./wagmi");
76
67
  var solana_1 = require("./solana");
@@ -80,11 +71,6 @@ var utils_1 = require("./privy/utils");
80
71
  /**
81
72
  * Constants for provider switching reasons
82
73
  */
83
- var PROVIDER_SWITCH_REASONS = {
84
- ADDRESS_MISMATCH: "Address mismatch indicates wallet switch",
85
- NO_ACCOUNTS: "Current provider has no accounts",
86
- CHECK_FAILED: "Could not check current provider accounts",
87
- };
88
74
  var FormoAnalytics = /** @class */ (function () {
89
75
  function FormoAnalytics(writeKey, options) {
90
76
  if (options === void 0) { options = {}; }
@@ -92,30 +78,7 @@ var FormoAnalytics = /** @class */ (function () {
92
78
  var _a, _b, _c;
93
79
  this.writeKey = writeKey;
94
80
  this.options = options;
95
- // Per-chain namespace state - isolates EVM and Solana connection state
96
- this._chainState = {
97
- evm: {},
98
- solana: {},
99
- };
100
- this._providerListenersMap = new Map();
101
- /**
102
- * EIP-6963 provider details discovered through the browser
103
- * This array contains all available providers with their metadata
104
- */
105
- this._providers = [];
106
- /**
107
- * Set of providers that have been tracked with event listeners
108
- * This is separate from _providers because:
109
- * - _providers contains all discovered providers (EIP-6963)
110
- * - _trackedProviders contains only providers that have been set up with listeners
111
- * - A provider can be discovered but not yet tracked (e.g., during initialization)
112
- * - A provider can be tracked but later removed from discovery
113
- */
114
- this._trackedProviders = new Set();
115
- // Flag to prevent concurrent processing of accountsChanged events
116
- this._processingAccountsChanged = false;
117
- // Set to efficiently track seen providers for deduplication and O(1) lookup
118
- this._seenProviders = new Set();
81
+ this._announcedConnect = new WeakMap();
119
82
  /**
120
83
  * Flag indicating if Wagmi mode is enabled
121
84
  * When true, EIP-1193 provider wrapping is skipped
@@ -168,6 +131,68 @@ var FormoAnalytics = /** @class */ (function () {
168
131
  enabled: ((_b = options.logger) === null || _b === void 0 ? void 0 : _b.enabled) || false,
169
132
  enabledLevels: ((_c = options.logger) === null || _c === void 0 ? void 0 : _c.levels) || [],
170
133
  });
134
+ this.evm = new EvmProviderRegistry_1.EvmProviderRegistry({
135
+ activeProvider: function () { return _this.wallet.provider; },
136
+ activeChainId: function () { return _this.wallet.evmChainId; },
137
+ knownEvmAddress: function () { return _this.wallet.evmAddress; },
138
+ // Whether central state should follow a provider's chain report is the
139
+ // SDK's call, not the registry's: it depends on which namespace is
140
+ // active. Recording it per provider is unconditional; syncing is not.
141
+ onChainObserved: function (provider, chainId) {
142
+ // Guarded on EVM already being active. `set()` makes whichever
143
+ // namespace it touches active, so syncing unconditionally let a
144
+ // background EVM wallet's chain report steal the slot from a live
145
+ // Solana wallet, and every later page or track event was attributed
146
+ // to the wrong wallet entirely.
147
+ if (provider === _this.wallet.provider &&
148
+ _this.wallet.activeNamespace === "evm" &&
149
+ _this.wallet.evmChainId !== chainId) {
150
+ _this.wallet.set("evm", { chainId: chainId });
151
+ }
152
+ },
153
+ });
154
+ this.wallet = new WalletStateStore_1.WalletStateStore({
155
+ isPersistedIdentityPurgeRequired: function () {
156
+ return _this.trackingPolicy.isPersistedIdentityPurgeRequired();
157
+ },
158
+ isPageExcluded: function () { return _this.trackingPolicy.isPageExcluded(); },
159
+ isTrackingSuppressed: function () { return _this.trackingPolicy.isTrackingSuppressed(); },
160
+ crossSubdomainCookies: function () { return _this.crossSubdomainCookies; },
161
+ providerChainId: function (provider) { return _this.evm.chainIdOf(provider); },
162
+ // A provider that stops being active has, from this SDK's point of
163
+ // view, ended its connection, so the connect it reported must stop
164
+ // counting. Otherwise toggling between two installed wallets silently
165
+ // loses every connect after the first.
166
+ onProviderDisplaced: function (previous) {
167
+ return _this.evmEvents.forgetAnnouncedConnect(previous);
168
+ },
169
+ });
170
+ this.evmRequests = new EvmRequestTracker_1.EvmRequestTracker(this.wallet, this.evm, {
171
+ isAutocaptureEnabled: function (t) { return _this.isAutocaptureEnabled(t); },
172
+ signature: function (params, properties) { return _this.signature(params, properties); },
173
+ transaction: function (params, properties) { return _this.transaction(params, properties); },
174
+ });
175
+ this.evmEvents = new EvmEventTracker_1.EvmEventTracker(this.wallet, this.evm, {
176
+ isAutocaptureEnabled: function (t) { return _this.isAutocaptureEnabled(t); },
177
+ isTrackingSuppressed: function () { return _this.isTrackingSuppressed(); },
178
+ willTrackEvent: function (chainId) { return _this.willTrackEvent(chainId); },
179
+ isWagmiMode: function () { return _this.isWagmiMode; },
180
+ connect: function (params, properties) { return _this.connect(params, properties); },
181
+ disconnect: function (params) { return _this.disconnect(params); },
182
+ chain: function (params, properties) { return _this.chain(params, properties); },
183
+ detect: function (params) { return _this.detect(params); },
184
+ registerRequestListeners: function (provider) {
185
+ return _this.evmRequests.registerRequestListeners(provider);
186
+ },
187
+ });
188
+ this.trackingPolicy = new TrackingPolicy_1.TrackingPolicy({
189
+ // All three read lazily. `options` is public and mutable, and the
190
+ // central chain moves as wallets connect and switch, so the policy
191
+ // must see both at decision time rather than at construction.
192
+ options: function () { return _this.options; },
193
+ hasOptedOut: function () { return _this.hasOptedOutTracking(); },
194
+ currentChainId: function () { return _this.currentChainId; },
195
+ });
171
196
  this.eventManager = new event_1.EventManager(new queue_1.EventQueue(this.config.writeKey, {
172
197
  apiHost: options.apiHost || constants_1.EVENTS_API_HOST,
173
198
  flushAt: options.flushAt,
@@ -203,7 +228,7 @@ var FormoAnalytics = /** @class */ (function () {
203
228
  provider = window.ethereum;
204
229
  }
205
230
  if (provider) {
206
- this.trackEIP1193Provider(provider);
231
+ this.evmEvents.trackEIP1193Provider(provider);
207
232
  }
208
233
  }
209
234
  // Initialize Solana manager if Solana options are provided
@@ -214,51 +239,73 @@ var FormoAnalytics = /** @class */ (function () {
214
239
  // Seed currentAddress/currentChainId from the persisted snapshot before
215
240
  // the first page hit queues so reload-time track()/page() carry the
216
241
  // wallet even before wagmi/EIP-1193 reconnection completes.
217
- this.loadActiveWallet();
242
+ this.wallet.load();
218
243
  this.trackPageHit();
219
244
  this.trackPageHits();
220
245
  }
221
246
  Object.defineProperty(FormoAnalytics.prototype, "_provider", {
222
- // EVM state accessors - EVM listener paths must use these instead of
223
- // currentAddress/currentChainId to avoid cross-namespace reads.
224
247
  get: function () {
225
- return this._chainState.evm.provider;
248
+ return this.wallet.provider;
226
249
  },
227
250
  set: function (value) {
228
- this._chainState.evm.provider = value;
251
+ this.wallet.provider = value;
229
252
  },
230
253
  enumerable: false,
231
254
  configurable: true
232
255
  });
233
256
  Object.defineProperty(FormoAnalytics.prototype, "_evmAddress", {
234
257
  get: function () {
235
- return this._chainState.evm.address;
258
+ return this.wallet.evmAddress;
236
259
  },
237
260
  enumerable: false,
238
261
  configurable: true
239
262
  });
240
263
  Object.defineProperty(FormoAnalytics.prototype, "_evmChainId", {
241
264
  get: function () {
242
- return this._chainState.evm.chainId;
265
+ return this.wallet.evmChainId;
266
+ },
267
+ enumerable: false,
268
+ configurable: true
269
+ });
270
+ Object.defineProperty(FormoAnalytics.prototype, "currentAddress", {
271
+ /**
272
+ * The wallet later events are attributed to, derived from whichever
273
+ * namespace last claimed the slot. Read by the wagmi and Privy
274
+ * integrations; the store is the only writer.
275
+ */
276
+ get: function () {
277
+ return this.wallet.address;
278
+ },
279
+ /**
280
+ * These stay writable.
281
+ *
282
+ * Both were public mutable fields, so a consumer assigning one is using a
283
+ * documented public API. Getter-only replacements would break that
284
+ * silently: TypeScript rejects the assignment, and plain JavaScript throws
285
+ * on an accessor with no setter. The writes forward into the store, which
286
+ * keeps a single owner without turning a refactor into a breaking change.
287
+ */
288
+ set: function (value) {
289
+ this.wallet.setActiveAddress(value);
290
+ },
291
+ enumerable: false,
292
+ configurable: true
293
+ });
294
+ Object.defineProperty(FormoAnalytics.prototype, "currentChainId", {
295
+ get: function () {
296
+ return this.wallet.chainId;
297
+ },
298
+ set: function (value) {
299
+ this.wallet.setActiveChainId(value);
243
300
  },
244
301
  enumerable: false,
245
302
  configurable: true
246
303
  });
247
- /**
248
- * Helper method to check if a provider is different from the currently active one
249
- * @param provider The provider to check
250
- * @returns true if there's a provider mismatch, false otherwise
251
- */
252
- FormoAnalytics.prototype.isProviderMismatch = function (provider) {
253
- // Only consider it a mismatch if we have an active provider AND the provider is different
254
- // This allows legitimate provider switching while preventing race conditions
255
- return this._provider != null && this._provider !== provider;
256
- };
257
304
  FormoAnalytics.init = function (writeKey, options) {
258
305
  return __awaiter(this, void 0, void 0, function () {
259
- var analytics, _a;
260
- return __generator(this, function (_b) {
261
- switch (_b.label) {
306
+ var analytics, discovered;
307
+ return __generator(this, function (_a) {
308
+ switch (_a.label) {
262
309
  case 0:
263
310
  (0, storage_1.initStorageManager)(writeKey);
264
311
  analytics = new FormoAnalytics(writeKey, options);
@@ -267,20 +314,17 @@ var FormoAnalytics = /** @class */ (function () {
267
314
  return [3 /*break*/, 5];
268
315
  case 1:
269
316
  if (!!analytics.isWagmiMode) return [3 /*break*/, 4];
270
- // Auto-detect wallet provider
271
- _a = analytics;
272
- return [4 /*yield*/, analytics.getProviders()];
317
+ return [4 /*yield*/, analytics.evmEvents.getProviders()];
273
318
  case 2:
274
- // Auto-detect wallet provider
275
- _a._providers = _b.sent();
276
- return [4 /*yield*/, analytics.detectWallets(analytics._providers)];
319
+ discovered = _a.sent();
320
+ return [4 /*yield*/, analytics.evmEvents.detectWallets(discovered)];
277
321
  case 3:
278
- _b.sent();
279
- analytics.trackProviders(analytics._providers);
322
+ _a.sent();
323
+ analytics.evmEvents.trackProviders(discovered);
280
324
  return [3 /*break*/, 5];
281
325
  case 4:
282
326
  logger_1.logger.info("FormoAnalytics: Skipping provider detection (Wagmi mode)");
283
- _b.label = 5;
327
+ _a.label = 5;
284
328
  case 5: return [2 /*return*/, analytics];
285
329
  }
286
330
  });
@@ -321,11 +365,7 @@ var FormoAnalytics = /** @class */ (function () {
321
365
  // address on subsequent track()/page() events for the rest of the
322
366
  // page lifetime, because they fall back to currentAddress. Keep the
323
367
  // EVM provider reference so tracking can resume on the next connect.
324
- this.currentAddress = undefined;
325
- this.currentChainId = undefined;
326
- var evmProvider = this._chainState.evm.provider;
327
- this._chainState = { evm: { provider: evmProvider }, solana: {} };
328
- this._activeNamespace = undefined;
368
+ this.wallet.reset();
329
369
  (0, storage_1.cookie)().remove(constants_1.LOCAL_ANONYMOUS_ID_KEY);
330
370
  (0, storage_1.cookie)().remove(constants_1.SESSION_USER_ID_KEY);
331
371
  (0, storage_1.cookie)().remove(session_1.SESSION_WALLET_DETECTED_KEY);
@@ -343,8 +383,19 @@ var FormoAnalytics = /** @class */ (function () {
343
383
  */
344
384
  FormoAnalytics.prototype.cleanup = function () {
345
385
  logger_1.logger.debug("FormoAnalytics: Cleaning up resources");
346
- // Drop buffered events so a torn-down instance can't flush later.
347
- this.eventManager.clear();
386
+ // Close the queue, don't just empty it. clear() only drops what is
387
+ // buffered at this instant; asynchronous work already in flight (event
388
+ // creation is async on every emit path) would still enqueue afterwards,
389
+ // and an empty queue flushes immediately. close() is terminal, so a
390
+ // continuation that outlives this instance cannot send with its stale
391
+ // options. See issue #339.
392
+ this.eventManager.close();
393
+ // Stop any receipt or batch-status polling: a torn-down instance must
394
+ // not keep asking a wallet about transactions nobody is listening for.
395
+ this.evmRequests.cleanup();
396
+ // Stop reacting to wallet announcements: a disposed instance must not
397
+ // wrap new providers or emit detect events.
398
+ this.evmEvents.cleanup();
348
399
  // Clean up Wagmi handler if present
349
400
  if (this.wagmiHandler) {
350
401
  this.wagmiHandler.cleanup();
@@ -357,9 +408,9 @@ var FormoAnalytics = /** @class */ (function () {
357
408
  }
358
409
  // Clean up EIP-1193 providers if not in Wagmi mode
359
410
  if (!this.isWagmiMode) {
360
- for (var _i = 0, _a = Array.from(this._trackedProviders); _i < _a.length; _i++) {
411
+ for (var _i = 0, _a = this.evm.trackedProviders(); _i < _a.length; _i++) {
361
412
  var provider = _a[_i];
362
- this.untrackProvider(provider);
413
+ this.evmEvents.untrackProvider(provider);
363
414
  }
364
415
  }
365
416
  // Tear down page-hit hooks: remove window listeners and silence the
@@ -416,6 +467,11 @@ var FormoAnalytics = /** @class */ (function () {
416
467
  logger_1.logger.info("connect() skipped: tracking is suppressed for this visitor or environment");
417
468
  return [2 /*return*/];
418
469
  }
470
+ // A new wallet now owns this namespace. Anything already tearing down
471
+ // the previous session must not undo this. An emitted connect always
472
+ // counts, even when the address is the one already there: a wallet that
473
+ // disconnects and reconnects leaves identical state.
474
+ this.wallet.observe(this.wallet.namespaceOf(chainId));
419
475
  this.setChainState(chainId, { address: validAddress });
420
476
  return [4 /*yield*/, this.trackEvent(constants_1.EventType.CONNECT, {
421
477
  chainId: chainId,
@@ -439,7 +495,7 @@ var FormoAnalytics = /** @class */ (function () {
439
495
  */
440
496
  FormoAnalytics.prototype.disconnect = function (params, properties, context, callback) {
441
497
  return __awaiter(this, void 0, void 0, function () {
442
- var chainId, address, isSolana, providerInfo, disconnectProperties;
498
+ var chainId, address, isSolana, providerInfo, disconnectProperties, namespace, before;
443
499
  return __generator(this, function (_a) {
444
500
  switch (_a.label) {
445
501
  case 0:
@@ -459,9 +515,25 @@ var FormoAnalytics = /** @class */ (function () {
459
515
  providerName: providerInfo.name,
460
516
  rdns: providerInfo.rdns,
461
517
  })), properties);
518
+ namespace = this.wallet.namespaceOf(chainId);
519
+ // Every teardown announces itself here, before anything is awaited,
520
+ // whichever path reached it - a provider event or a direct call from the
521
+ // host app. A connect observation that began earlier is invalidated; one
522
+ // that begins after this point is a genuine reconnect and still reports.
523
+ this.wallet.beginDisconnect(namespace);
524
+ before = this.wallet.snapshot(namespace);
462
525
  return [4 /*yield*/, this.trackEvent(constants_1.EventType.DISCONNECT, __assign(__assign({}, (chainId && { chainId: chainId })), (address && { address: address })), disconnectProperties, context, callback)];
463
526
  case 1:
464
527
  _a.sent();
528
+ // If a wallet claimed this namespace while the event was being built, a
529
+ // new session owns it and this cleanup is stale. Running it anyway would
530
+ // wipe that session's state AND its reported-connect record, so a later
531
+ // wallet signal would emit a second connect for a connection that is
532
+ // already reported. See issue #344.
533
+ if (!this.wallet.isUnchangedSince(namespace, before)) {
534
+ logger_1.logger.info("Disconnect: A new session claimed this namespace while the event was in flight; leaving its state intact");
535
+ return [2 /*return*/];
536
+ }
465
537
  // Clear the disconnecting chain's namespace state.
466
538
  // Per-chain isolation ensures a Solana disconnect never wipes EVM state (and vice versa).
467
539
  this.clearChainState(chainId);
@@ -471,6 +543,78 @@ var FormoAnalytics = /** @class */ (function () {
471
543
  });
472
544
  });
473
545
  };
546
+ /** @see WalletStateStore.set */
547
+ FormoAnalytics.prototype.setChainState = function (namespaceOrChainId, update) {
548
+ this.wallet.set(namespaceOrChainId, update);
549
+ };
550
+ /** @see WalletStateStore.clear */
551
+ FormoAnalytics.prototype.clearChainState = function (namespaceOrChainId) {
552
+ this.wallet.clear(namespaceOrChainId);
553
+ };
554
+ /**
555
+ * Record validated wallet/chain state WITHOUT emitting an event.
556
+ *
557
+ * Integrations must call this on every connect / chain change / disconnect,
558
+ * even when the matching autocapture event is disabled, or the exclusion
559
+ * gate (which keys off the central chain, not the event payload) can be
560
+ * bypassed. Stays on the class because integrations bind to it.
561
+ * @see WalletStateStore.syncWalletState
562
+ */
563
+ FormoAnalytics.prototype.syncWalletState = function (params) {
564
+ this.wallet.syncWalletState(params);
565
+ };
566
+ /** @see WalletStateStore.clearProvider */
567
+ FormoAnalytics.prototype.clearActiveProvider = function () {
568
+ this.wallet.clearProvider();
569
+ };
570
+ /** @see WalletStateStore.backfill */
571
+ FormoAnalytics.prototype.backfillActiveWallet = function (address, chainId, provider) {
572
+ this.wallet.backfill(address, chainId, provider);
573
+ };
574
+ /** @see WalletStateStore.clearStaleEvmWalletOnSwitchWhileSuppressed */
575
+ FormoAnalytics.prototype.clearStaleEvmWalletOnSwitchWhileSuppressed = function (address) {
576
+ this.wallet.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
577
+ };
578
+ /** @see EvmProviderRegistry.addListener */
579
+ FormoAnalytics.prototype.addProviderListener = function (provider, event, listener) {
580
+ this.evm.addListener(provider, event, listener);
581
+ };
582
+ /** @see EvmProviderRegistry.removeListeners */
583
+ FormoAnalytics.prototype.removeProviderListeners = function (provider) {
584
+ this.evm.removeListeners(provider);
585
+ };
586
+ /** @see EvmProviderRegistry.infoFor */
587
+ FormoAnalytics.prototype.getProviderInfo = function (provider) {
588
+ return this.evm.infoFor(provider);
589
+ };
590
+ /** @see EvmProviderRegistry.isWrapped */
591
+ FormoAnalytics.prototype.isProviderAlreadyWrapped = function (provider, currentRequest) {
592
+ return this.evm.isWrapped(provider, currentRequest);
593
+ };
594
+ /** @see EvmProviderRegistry.resolveChainId */
595
+ FormoAnalytics.prototype.resolveChainIdForProvider = function (provider) {
596
+ return this.evm.resolveChainId(provider);
597
+ };
598
+ /** @see EvmProviderRegistry.rememberChain */
599
+ FormoAnalytics.prototype.rememberProviderChain = function (provider, chainId) {
600
+ this.evm.rememberChain(provider, chainId);
601
+ };
602
+ /** @see EvmProviderRegistry.addressOf */
603
+ FormoAnalytics.prototype.getAddress = function (provider) {
604
+ return __awaiter(this, void 0, void 0, function () {
605
+ return __generator(this, function (_a) {
606
+ return [2 /*return*/, this.evm.addressOf(provider)];
607
+ });
608
+ });
609
+ };
610
+ /** @see EvmProviderRegistry.accountsOf */
611
+ FormoAnalytics.prototype.getAccounts = function (provider) {
612
+ return __awaiter(this, void 0, void 0, function () {
613
+ return __generator(this, function (_a) {
614
+ return [2 /*return*/, this.evm.accountsOf(provider)];
615
+ });
616
+ });
617
+ };
474
618
  /**
475
619
  * Emits a chain network change event.
476
620
  * @param {ChainID} params.chainId
@@ -612,14 +756,14 @@ var FormoAnalytics = /** @class */ (function () {
612
756
  // valid (activating a Solana wallet while an excluded EVM chain id is
613
757
  // still current). By the time the Privy path reaches this guard it has
614
758
  // already reconciled, so each inner identify is judged on the right chain.
615
- if (this.isTrackingSuppressed() || this.isCurrentChainExcluded()) {
759
+ if (this.isTrackingSuppressed() || this.trackingPolicy.isChainExcluded()) {
616
760
  logger_1.logger.info("identify() skipped: tracking is suppressed for this visitor, environment, or chain");
617
761
  return [2 /*return*/];
618
762
  }
619
763
  if (!!params) return [3 /*break*/, 12];
620
764
  // If no params provided, auto-identify
621
- logger_1.logger.info("Auto-identifying with providers:", this._providers.map(function (p) { return p.info.name; }));
622
- _i = 0, _a = this._providers;
765
+ logger_1.logger.info("Auto-identifying with providers:", this.evm.all.map(function (p) { return p.info.name; }));
766
+ _i = 0, _a = this.evm.all;
623
767
  _d.label = 3;
624
768
  case 3:
625
769
  if (!(_i < _a.length)) return [3 /*break*/, 11];
@@ -691,8 +835,7 @@ var FormoAnalytics = /** @class */ (function () {
691
835
  // attribution. Gating address and userId together prevents leaving the
692
836
  // active address paired with a different wallet's user id.
693
837
  if (setActive !== false) {
694
- this.currentAddress = validAddress;
695
- this.persistActiveWallet();
838
+ this.wallet.setActiveAddress(validAddress);
696
839
  if (userId) {
697
840
  this.currentUserId = userId;
698
841
  domain = (0, cookiePolicy_1.getIdentityCookieDomain)(this.crossSubdomainCookies);
@@ -779,8 +922,7 @@ var FormoAnalytics = /** @class */ (function () {
779
922
  return;
780
923
  var currentIsSolana = (0, solana_1.isSolanaChainId)(this.currentChainId);
781
924
  if (walletIsSolana !== currentIsSolana) {
782
- this.currentChainId = undefined;
783
- this.persistActiveWallet();
925
+ this.wallet.setActiveChainId(undefined);
784
926
  }
785
927
  };
786
928
  /**
@@ -846,6 +988,18 @@ var FormoAnalytics = /** @class */ (function () {
846
988
  * Opt out of tracking.
847
989
  * @returns {void}
848
990
  */
991
+ /**
992
+ * Whether an event carrying this chain would currently be sent.
993
+ *
994
+ * Exposed for integrations that keep their own "already reported" state.
995
+ * `syncWalletState()` can accept a wallet that `trackEvent()` then drops -
996
+ * `tracking: false`, or a chain in `excludeChains` - and an integration that
997
+ * marked it as reported would stay silent about that wallet even after the
998
+ * configuration changed to allow it.
999
+ */
1000
+ FormoAnalytics.prototype.willTrackEvent = function (chainId) {
1001
+ return this.shouldTrack(chainId);
1002
+ };
849
1003
  FormoAnalytics.prototype.optOutTracking = function () {
850
1004
  logger_1.logger.info("Opting out of tracking");
851
1005
  // Set opt-out flag in persistent storage using direct cookie access
@@ -863,9 +1017,15 @@ var FormoAnalytics = /** @class */ (function () {
863
1017
  * @returns {void}
864
1018
  */
865
1019
  FormoAnalytics.prototype.optInTracking = function () {
1020
+ var _a;
866
1021
  logger_1.logger.info("Opting back into tracking");
867
1022
  // Remove opt-out flag
868
1023
  (0, consent_1.removeConsentFlag)(this.writeKey, constants_1.CONSENT_OPT_OUT_KEY);
1024
+ // A wallet connected while opted out was declined by syncWalletState, and
1025
+ // an unchanged wagmi connection produces no status or chain update to
1026
+ // retry on. Without this, opting back in leaves that wallet invisible for
1027
+ // the rest of the page load.
1028
+ (_a = this.wagmiHandler) === null || _a === void 0 ? void 0 : _a.retryAdoption();
869
1029
  logger_1.logger.info("Successfully opted back into tracking");
870
1030
  };
871
1031
  /**
@@ -878,1305 +1038,211 @@ var FormoAnalytics = /** @class */ (function () {
878
1038
  /*
879
1039
  SDK tracking and event listener functions
880
1040
  */
1041
+ FormoAnalytics.prototype.onLocationChange = function () {
1042
+ return __awaiter(this, void 0, void 0, function () {
1043
+ var _a;
1044
+ return __generator(this, function (_b) {
1045
+ if (this._currentUrl !== window.location.href) {
1046
+ this._currentUrl = window.location.href;
1047
+ // Host/path exclusions are evaluated per navigation, so a SPA can leave
1048
+ // an excluded route and become trackable without any wallet event
1049
+ // firing. The wagmi handler only observes *changes*, so an unchanged
1050
+ // connection it was forced to decline earlier would stay invisible for
1051
+ // the rest of the page load. Give it a chance to adopt it now.
1052
+ (_a = this.wagmiHandler) === null || _a === void 0 ? void 0 : _a.retryAdoption();
1053
+ this.trackPageHit();
1054
+ }
1055
+ return [2 /*return*/];
1056
+ });
1057
+ });
1058
+ };
1059
+ FormoAnalytics.prototype.trackPageHits = function () {
1060
+ var _this = this;
1061
+ // Install a single, instance-agnostic wrapper around history.pushState /
1062
+ // replaceState so concurrent SDK instances (React Strict Mode, HMR) don't
1063
+ // each stack their own wrapper - which would dispatch N synthetic events
1064
+ // per navigation and produce O(N^2) onLocationChange calls. The wrapper
1065
+ // dispatches once; per-instance bookkeeping is done by per-instance
1066
+ // listeners that each register/unregister themselves.
1067
+ FormoAnalytics.installHistoryHooksOnce();
1068
+ this._onPopStateListener = function () { return _this.onLocationChange(); };
1069
+ this._onLocationChangeListener = function () { return _this.onLocationChange(); };
1070
+ window.addEventListener("popstate", this._onPopStateListener);
1071
+ window.addEventListener("locationchange", this._onLocationChangeListener);
1072
+ };
881
1073
  /**
882
- * Track an EIP-1193 provider by wrapping its request method and adding event listeners
883
- * Note: This is only used in non-Wagmi mode. When Wagmi is enabled, all tracking
884
- * happens through Wagmi's connector system instead of EIP-1193/EIP-6963.
885
- * @param provider The EIP-1193 provider to track
1074
+ * Wrap history.pushState / replaceState exactly once per `history` object,
1075
+ * regardless of how many SDK instances are constructed. Uses a Symbol
1076
+ * marker so we recognize our own wrapper across module reloads in HMR.
886
1077
  */
887
- FormoAnalytics.prototype.trackEIP1193Provider = function (provider) {
888
- logger_1.logger.info("trackEIP1193Provider", provider);
889
- // Defensive check: Skip provider tracking in Wagmi mode
890
- // This should never be called in Wagmi mode due to guards in init(),
891
- // but we check here for safety in case of future code changes
892
- if (this.isWagmiMode) {
893
- logger_1.logger.debug("trackEIP1193Provider: Skipping EIP-1193 provider tracking (Wagmi mode - using connector system instead)");
1078
+ FormoAnalytics.installHistoryHooksOnce = function () {
1079
+ if (typeof history === "undefined" || typeof window === "undefined")
894
1080
  return;
895
- }
896
- try {
897
- // Validate provider exists and has required methods
898
- if (!(0, provider_1.isValidProvider)(provider)) {
899
- logger_1.logger.warn("trackEIP1193Provider: Invalid provider - missing required methods");
900
- return;
901
- }
902
- if (this._trackedProviders.has(provider)) {
903
- logger_1.logger.warn("trackEIP1193Provider: Provider already tracked");
904
- return;
905
- }
906
- // CRITICAL: Always register accountsChanged for state management
907
- // This ensures currentAddress, currentChainId, and _provider are always up-to-date
908
- // Event emission is controlled conditionally inside the handlers
909
- this.registerAccountsChangedListener(provider);
910
- // Register other listeners based on autocapture configuration
911
- if (this.isAutocaptureEnabled("chain")) {
912
- this.registerChainChangedListener(provider);
913
- }
914
- if (this.isAutocaptureEnabled("connect")) {
915
- this.registerConnectListener(provider);
916
- }
917
- if (this.isAutocaptureEnabled("signature") || this.isAutocaptureEnabled("transaction")) {
918
- this.registerRequestListeners(provider);
919
- }
920
- else {
921
- logger_1.logger.debug("TrackProvider: Skipping request wrapping (both signature and transaction autocapture disabled)");
922
- }
923
- if (this.isAutocaptureEnabled("disconnect")) {
924
- this.registerDisconnectListener(provider);
925
- }
926
- // Only add to tracked providers after all listeners are successfully registered
927
- this._trackedProviders.add(provider);
928
- }
929
- catch (error) {
930
- logger_1.logger.error("Error tracking provider:", error);
931
- }
932
- };
933
- FormoAnalytics.prototype.trackProviders = function (providers) {
934
- try {
935
- for (var _i = 0, providers_1 = providers; _i < providers_1.length; _i++) {
936
- var eip6963ProviderDetail = providers_1[_i];
937
- var provider = eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.provider;
938
- if (provider && !this._trackedProviders.has(provider)) {
939
- this.trackEIP1193Provider(provider);
940
- }
1081
+ var marker = Symbol.for("formo.historyWrapped");
1082
+ if (history[marker])
1083
+ return;
1084
+ history[marker] = true;
1085
+ var dispatch = function () { return window.dispatchEvent(new window.Event("locationchange")); };
1086
+ var oldPushState = history.pushState;
1087
+ history.pushState = function pushState() {
1088
+ var args = [];
1089
+ for (var _i = 0; _i < arguments.length; _i++) {
1090
+ args[_i] = arguments[_i];
941
1091
  }
942
- }
943
- catch (error) {
944
- logger_1.logger.error("Failed to track EIP-6963 providers during initialization:", error);
945
- }
946
- };
947
- FormoAnalytics.prototype.addProviderListener = function (provider, event, listener) {
948
- var map = this._providerListenersMap.get(provider) || {};
949
- map[event] = listener;
950
- this._providerListenersMap.set(provider, map);
951
- };
952
- FormoAnalytics.prototype.registerAccountsChangedListener = function (provider) {
953
- var _this = this;
954
- logger_1.logger.info("registerAccountsChangedListener");
955
- var listener = function () {
1092
+ var ret = oldPushState.apply(this, args);
1093
+ dispatch();
1094
+ return ret;
1095
+ };
1096
+ var oldReplaceState = history.replaceState;
1097
+ history.replaceState = function replaceState() {
956
1098
  var args = [];
957
1099
  for (var _i = 0; _i < arguments.length; _i++) {
958
1100
  args[_i] = arguments[_i];
959
1101
  }
960
- return _this.onAccountsChanged(provider, args[0]);
1102
+ var ret = oldReplaceState.apply(this, args);
1103
+ dispatch();
1104
+ return ret;
961
1105
  };
962
- provider.on("accountsChanged", listener);
963
- this.addProviderListener(provider, "accountsChanged", listener);
964
1106
  };
965
- FormoAnalytics.prototype.onAccountsChanged = function (provider, accounts) {
1107
+ FormoAnalytics.prototype.trackPageHit = function (category, name, properties, context, callback) {
1108
+ return __awaiter(this, void 0, void 0, function () {
1109
+ var _this = this;
1110
+ return __generator(this, function (_a) {
1111
+ if (!this.shouldTrack()) {
1112
+ logger_1.logger.info("Track page hit: Skipping event due to tracking configuration");
1113
+ return [2 /*return*/];
1114
+ }
1115
+ setTimeout(function () {
1116
+ // Drop in-flight page hits from an SDK instance that was torn down
1117
+ // between scheduling and firing (e.g. provider remount in React Strict
1118
+ // Mode / HMR). Otherwise the orphan instance would queue a page event
1119
+ // here with its stale, never-populated `currentAddress`.
1120
+ if (_this._pageHooksDisposed)
1121
+ return;
1122
+ (function () { return __awaiter(_this, void 0, void 0, function () {
1123
+ var e_2;
1124
+ return __generator(this, function (_a) {
1125
+ switch (_a.label) {
1126
+ case 0:
1127
+ _a.trys.push([0, 2, , 3]);
1128
+ return [4 /*yield*/, this.trackEvent(constants_1.EventType.PAGE, {
1129
+ category: category,
1130
+ name: name,
1131
+ }, properties, context, callback)];
1132
+ case 1:
1133
+ _a.sent();
1134
+ return [3 /*break*/, 3];
1135
+ case 2:
1136
+ e_2 = _a.sent();
1137
+ logger_1.logger.error("Formo: Failed to track page hit", e_2);
1138
+ return [3 /*break*/, 3];
1139
+ case 3: return [2 /*return*/];
1140
+ }
1141
+ });
1142
+ }); })();
1143
+ }, 300);
1144
+ return [2 /*return*/];
1145
+ });
1146
+ });
1147
+ };
1148
+ FormoAnalytics.prototype.trackEvent = function (type, payload, properties, context, callback) {
966
1149
  return __awaiter(this, void 0, void 0, function () {
1150
+ var error_1;
967
1151
  return __generator(this, function (_a) {
968
1152
  switch (_a.label) {
969
1153
  case 0:
970
- logger_1.logger.info("onAccountsChanged", accounts);
971
- // Prevent concurrent processing of accountsChanged events to avoid race conditions
972
- if (this._processingAccountsChanged) {
973
- logger_1.logger.debug("OnAccountsChanged: Already processing accountsChanged, skipping", {
974
- provider: this.getProviderInfo(provider).name,
975
- });
1154
+ _a.trys.push([0, 2, , 3]);
1155
+ // Gate on the chain the event actually carries. `connect`, `disconnect`,
1156
+ // `chain`, `signature` and `transaction` all put one in the payload, and
1157
+ // it is authoritative: it can name a provider or a wagmi mutation chain
1158
+ // that is not the active one. Events without a chain (page, track,
1159
+ // identify) fall back to the central value.
1160
+ if (!this.shouldTrack(payload === null || payload === void 0 ? void 0 : payload.chainId)) {
1161
+ logger_1.logger.info("Skipping ".concat(type, " event due to tracking configuration"));
976
1162
  return [2 /*return*/];
977
1163
  }
978
- this._processingAccountsChanged = true;
979
- _a.label = 1;
1164
+ return [4 /*yield*/, this.eventManager.addEvent(__assign(__assign({ type: type }, payload), { properties: properties, context: context, callback: callback }), this.currentAddress, this.currentUserId)];
980
1165
  case 1:
981
- _a.trys.push([1, , 3, 4]);
982
- return [4 /*yield*/, this._handleAccountsChanged(provider, accounts)];
983
- case 2:
984
1166
  _a.sent();
985
- return [3 /*break*/, 4];
986
- case 3:
987
- this._processingAccountsChanged = false;
988
- return [7 /*endfinally*/];
989
- case 4: return [2 /*return*/];
1167
+ return [3 /*break*/, 3];
1168
+ case 2:
1169
+ error_1 = _a.sent();
1170
+ logger_1.logger.error("Error tracking event:", error_1);
1171
+ return [3 /*break*/, 3];
1172
+ case 3: return [2 /*return*/];
990
1173
  }
991
1174
  });
992
1175
  });
993
1176
  };
994
1177
  /**
995
- * Handles changes to the accounts of a given EIP-1193 provider.
1178
+ * Visitor-level tracking suppression.
996
1179
  *
997
- * @param provider - The EIP-1193 provider whose accounts have changed.
998
- * @param accounts - The new array of account addresses. An empty array indicates a disconnect.
999
- * @returns A promise that resolves when the account change has been processed.
1180
+ * True when the SDK must not persist identity/session/chain state or send
1181
+ * events for this visitor. Public entry points that write state before
1182
+ * reaching the `shouldTrack()` gate (identify / connect / detect) check this
1183
+ * first, so a suppressed visitor leaves no cookies or session state.
1000
1184
  *
1001
- * If the accounts array is empty and the provider is the active provider, this method triggers
1002
- * a disconnect flow. Otherwise, it updates the state to reflect the new accounts as needed.
1185
+ * The rule lives in `TrackingPolicy`; this stays on the class because it is
1186
+ * part of the surface integrations bind to.
1187
+ * @internal Also read by `identifyPrivyUser` (via a structural cast) so the
1188
+ * Privy sync skips chain reconciliation and emission for suppressed visitors.
1189
+ */
1190
+ FormoAnalytics.prototype.isTrackingSuppressed = function () {
1191
+ return this.trackingPolicy.isTrackingSuppressed();
1192
+ };
1193
+ /** @see TrackingPolicy.shouldTrack */
1194
+ FormoAnalytics.prototype.shouldTrack = function (eventChainId) {
1195
+ return this.trackingPolicy.shouldTrack({ chainId: eventChainId });
1196
+ };
1197
+ /**
1198
+ * Check if a specific wallet event type is enabled for autocapture
1199
+ * @param eventType The wallet event type to check
1200
+ * @returns {boolean} True if the event type should be autocaptured
1003
1201
  */
1004
- FormoAnalytics.prototype._handleAccountsChanged = function (provider, accounts) {
1202
+ FormoAnalytics.prototype.isAutocaptureEnabled = function (eventType) {
1203
+ return this.trackingPolicy.isAutocaptureEnabled(eventType);
1204
+ };
1205
+ Object.defineProperty(FormoAnalytics.prototype, "providers", {
1206
+ /*
1207
+ Utility functions
1208
+ */
1209
+ get: function () {
1210
+ return this.evm.all;
1211
+ },
1212
+ enumerable: false,
1213
+ configurable: true
1214
+ });
1215
+ Object.defineProperty(FormoAnalytics.prototype, "provider", {
1216
+ get: function () {
1217
+ return this._provider;
1218
+ },
1219
+ enumerable: false,
1220
+ configurable: true
1221
+ });
1222
+ Object.defineProperty(FormoAnalytics.prototype, "solana", {
1223
+ /**
1224
+ * Access the Solana integration manager.
1225
+ * Lazily creates one if not already initialized.
1226
+ *
1227
+ * @example
1228
+ * ```tsx
1229
+ * formo.solana.setStore(client.store);
1230
+ * formo.solana.setCluster("devnet");
1231
+ * // For signatures, use formo.signature() directly
1232
+ * ```
1233
+ */
1234
+ get: function () {
1235
+ if (!this.solanaManager) {
1236
+ this.solanaManager = new SolanaManager_1.SolanaManager(this);
1237
+ }
1238
+ return this.solanaManager;
1239
+ },
1240
+ enumerable: false,
1241
+ configurable: true
1242
+ });
1243
+ FormoAnalytics.prototype.getCurrentChainId = function (provider) {
1005
1244
  return __awaiter(this, void 0, void 0, function () {
1006
- var error_1, address, currentStoredAddress, newProviderAddress, activeProviderAccounts, error_2, nextChainId, wasDisconnected, providerInfo, effectiveChainId;
1007
- return __generator(this, function (_a) {
1008
- switch (_a.label) {
1009
- case 0:
1010
- if (!(accounts.length === 0)) return [3 /*break*/, 9];
1011
- if (!(this._provider === provider)) return [3 /*break*/, 7];
1012
- logger_1.logger.info("OnAccountsChanged: Detecting disconnect, current state:", {
1013
- evmAddress: this._evmAddress,
1014
- evmChainId: this._evmChainId,
1015
- providerMatch: this._provider === provider,
1016
- });
1017
- if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 5];
1018
- _a.label = 1;
1019
- case 1:
1020
- _a.trys.push([1, 3, , 4]);
1021
- // Pass EVM state explicitly to ensure we have the data for the disconnect event
1022
- return [4 /*yield*/, this.disconnect({
1023
- chainId: this._evmChainId,
1024
- address: this._evmAddress,
1025
- })];
1026
- case 2:
1027
- // Pass EVM state explicitly to ensure we have the data for the disconnect event
1028
- _a.sent();
1029
- return [3 /*break*/, 4];
1030
- case 3:
1031
- error_1 = _a.sent();
1032
- logger_1.logger.error("Failed to disconnect provider on accountsChanged", error_1);
1033
- return [3 /*break*/, 4];
1034
- case 4: return [3 /*break*/, 6];
1035
- case 5:
1036
- logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped (autocapture.disconnect: false)");
1037
- // Still clear state even if not tracking the event
1038
- this.clearChainState('evm');
1039
- _a.label = 6;
1040
- case 6: return [3 /*break*/, 8];
1041
- case 7:
1042
- logger_1.logger.info("OnAccountsChanged: Ignoring disconnect for non-active provider");
1043
- _a.label = 8;
1044
- case 8: return [2 /*return*/];
1045
- case 9:
1046
- address = (0, address_1.validateAndChecksumAddress)(accounts[0]);
1047
- if (!address) {
1048
- logger_1.logger.warn("onAccountsChanged: Invalid address received", accounts[0]);
1049
- return [2 /*return*/];
1050
- }
1051
- if (!(this._provider && this._provider !== provider)) return [3 /*break*/, 25];
1052
- currentStoredAddress = this._evmAddress;
1053
- newProviderAddress = (0, address_1.validateAndChecksumAddress)(address);
1054
- logger_1.logger.info("OnAccountsChanged: Different provider attempting to connect", {
1055
- activeProvider: this.getProviderInfo(this._provider).name,
1056
- eventProvider: this.getProviderInfo(provider).name,
1057
- currentStoredAddress: currentStoredAddress,
1058
- newProviderAddress: newProviderAddress,
1059
- });
1060
- _a.label = 10;
1061
- case 10:
1062
- _a.trys.push([10, 21, , 25]);
1063
- return [4 /*yield*/, this.getAccounts(this._provider)];
1064
- case 11:
1065
- activeProviderAccounts = _a.sent();
1066
- logger_1.logger.info("OnAccountsChanged: Checking current provider accounts", {
1067
- activeProvider: this.getProviderInfo(this._provider).name,
1068
- accountsLength: activeProviderAccounts
1069
- ? activeProviderAccounts.length
1070
- : 0,
1071
- accounts: activeProviderAccounts,
1072
- });
1073
- if (!(activeProviderAccounts && activeProviderAccounts.length > 0)) return [3 /*break*/, 17];
1074
- if (!(newProviderAddress &&
1075
- currentStoredAddress &&
1076
- newProviderAddress !== currentStoredAddress)) return [3 /*break*/, 15];
1077
- logger_1.logger.info("OnAccountsChanged: Different address detected, switching providers despite current provider having accounts", {
1078
- activeProvider: this.getProviderInfo(this._provider).name,
1079
- eventProvider: this.getProviderInfo(provider).name,
1080
- currentAddress: currentStoredAddress,
1081
- newAddress: newProviderAddress,
1082
- reason: PROVIDER_SWITCH_REASONS.ADDRESS_MISMATCH,
1083
- });
1084
- if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 13];
1085
- return [4 /*yield*/, this.disconnect({
1086
- chainId: this._evmChainId,
1087
- address: this._evmAddress,
1088
- })];
1089
- case 12:
1090
- _a.sent();
1091
- return [3 /*break*/, 14];
1092
- case 13:
1093
- logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped during provider switch (autocapture.disconnect: false)");
1094
- // Still clear state even if not tracking the event
1095
- this.clearChainState('evm');
1096
- _a.label = 14;
1097
- case 14:
1098
- // Clear state and let the new provider become active
1099
- this.clearActiveProvider();
1100
- return [3 /*break*/, 16];
1101
- case 15:
1102
- logger_1.logger.info("OnAccountsChanged: Current provider still has accounts and same address, ignoring new provider", {
1103
- activeProvider: this.getProviderInfo(this._provider).name,
1104
- eventProvider: this.getProviderInfo(provider).name,
1105
- activeProviderAccountsCount: activeProviderAccounts.length,
1106
- currentAddress: currentStoredAddress,
1107
- newAddress: newProviderAddress,
1108
- });
1109
- return [2 /*return*/];
1110
- case 16: return [3 /*break*/, 20];
1111
- case 17:
1112
- logger_1.logger.info("OnAccountsChanged: Current provider has no accounts, switching to new provider", {
1113
- oldProvider: this.getProviderInfo(this._provider).name,
1114
- newProvider: this.getProviderInfo(provider).name,
1115
- reason: PROVIDER_SWITCH_REASONS.NO_ACCOUNTS,
1116
- });
1117
- if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 19];
1118
- return [4 /*yield*/, this.disconnect({
1119
- chainId: this._evmChainId,
1120
- address: this._evmAddress,
1121
- })];
1122
- case 18:
1123
- _a.sent();
1124
- return [3 /*break*/, 20];
1125
- case 19:
1126
- logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped for old provider (autocapture.disconnect: false)");
1127
- // Still clear state even if not tracking the event
1128
- this.clearChainState('evm');
1129
- _a.label = 20;
1130
- case 20: return [3 /*break*/, 25];
1131
- case 21:
1132
- error_2 = _a.sent();
1133
- logger_1.logger.warn("OnAccountsChanged: Could not check current provider accounts, switching to new provider", {
1134
- error: error_2 instanceof Error ? error_2.message : String(error_2),
1135
- errorType: error_2 instanceof Error ? error_2.constructor.name : typeof error_2,
1136
- oldProvider: this._provider
1137
- ? this.getProviderInfo(this._provider).name
1138
- : "unknown",
1139
- newProvider: this.getProviderInfo(provider).name,
1140
- reason: PROVIDER_SWITCH_REASONS.CHECK_FAILED,
1141
- });
1142
- if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 23];
1143
- return [4 /*yield*/, this.disconnect({
1144
- chainId: this._evmChainId,
1145
- address: this._evmAddress,
1146
- })];
1147
- case 22:
1148
- _a.sent();
1149
- return [3 /*break*/, 24];
1150
- case 23:
1151
- logger_1.logger.debug("OnAccountsChanged: Disconnect event skipped for failed provider check (autocapture.disconnect: false)");
1152
- // Still clear state even if not tracking the event
1153
- this.clearChainState('evm');
1154
- _a.label = 24;
1155
- case 24: return [3 /*break*/, 25];
1156
- case 25:
1157
- // Set provider if none exists (first connection)
1158
- if (!this._provider) {
1159
- this._provider = provider;
1160
- }
1161
- // If both the provider and address are the same, no-op
1162
- if (this._provider === provider && address === this._evmAddress) {
1163
- return [2 /*return*/];
1164
- }
1165
- return [4 /*yield*/, this.getCurrentChainId(provider)];
1166
- case 26:
1167
- nextChainId = _a.sent();
1168
- wasDisconnected = !this._evmAddress;
1169
- // Update state regardless of whether connect *event* tracking is enabled,
1170
- // so disconnect events keep valid address/chainId values. (excludeChains is
1171
- // NOT suppression - it still updates state so currentChainId can gate
1172
- // events.)
1173
- if (this.isTrackingSuppressed()) {
1174
- this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
1175
- }
1176
- else {
1177
- this.setChainState('evm', { address: address, chainId: nextChainId });
1178
- }
1179
- providerInfo = this.getProviderInfo(provider);
1180
- effectiveChainId = nextChainId || 0;
1181
- if (this.isAutocaptureEnabled("connect")) {
1182
- logger_1.logger.info("OnAccountsChanged: Detected wallet connection, emitting connect event", {
1183
- chainId: nextChainId,
1184
- address: address,
1185
- wasDisconnected: wasDisconnected,
1186
- providerName: providerInfo.name,
1187
- rdns: providerInfo.rdns,
1188
- hasChainId: !!nextChainId,
1189
- });
1190
- if (effectiveChainId === 0) {
1191
- logger_1.logger.info("OnAccountsChanged: Using fallback chainId 0 for connect event");
1192
- }
1193
- this.connect({
1194
- chainId: effectiveChainId,
1195
- address: address,
1196
- }, {
1197
- providerName: providerInfo.name,
1198
- rdns: providerInfo.rdns,
1199
- }).catch(function (error) {
1200
- logger_1.logger.error("Failed to track connect event during account change:", error);
1201
- });
1202
- }
1203
- else {
1204
- logger_1.logger.debug("OnAccountsChanged: Connect event skipped (autocapture.connect: false)", {
1205
- chainId: nextChainId,
1206
- address: address,
1207
- providerName: providerInfo.name,
1208
- });
1209
- }
1210
- return [2 /*return*/];
1211
- }
1212
- });
1213
- });
1214
- };
1215
- FormoAnalytics.prototype.registerChainChangedListener = function (provider) {
1216
- var _this = this;
1217
- logger_1.logger.info("registerChainChangedListener");
1218
- var listener = function () {
1219
- var args = [];
1220
- for (var _i = 0; _i < arguments.length; _i++) {
1221
- args[_i] = arguments[_i];
1222
- }
1223
- return _this.onChainChanged(provider, args[0]);
1224
- };
1225
- provider.on("chainChanged", listener);
1226
- this.addProviderListener(provider, "chainChanged", listener);
1227
- };
1228
- FormoAnalytics.prototype.onChainChanged = function (provider, chainIdHex) {
1229
- return __awaiter(this, void 0, void 0, function () {
1230
- var nextChainId;
1231
- return __generator(this, function (_a) {
1232
- logger_1.logger.info("onChainChanged", chainIdHex);
1233
- nextChainId = (0, chain_1.parseChainId)(chainIdHex);
1234
- // Only handle chain changes for the active provider (or if none is set yet)
1235
- if (this.isProviderMismatch(provider)) {
1236
- this.handleProviderMismatch(provider);
1237
- }
1238
- // Chain changes only matter for connected users
1239
- if (!this._evmAddress) {
1240
- logger_1.logger.info("OnChainChanged: No current address, user appears disconnected");
1241
- return [2 /*return*/, Promise.resolve()];
1242
- }
1243
- // Set provider if none exists
1244
- if (!this._provider) {
1245
- this._provider = provider;
1246
- }
1247
- this.setChainState('evm', { chainId: nextChainId });
1248
- try {
1249
- // This is just a chain change since we already confirmed _evmAddress exists
1250
- if (this.isAutocaptureEnabled("chain")) {
1251
- return [2 /*return*/, this.chain({
1252
- chainId: nextChainId,
1253
- address: this._evmAddress,
1254
- })];
1255
- }
1256
- else {
1257
- logger_1.logger.debug("OnChainChanged: Chain event skipped (autocapture.chain: false)", {
1258
- chainId: this._evmChainId,
1259
- address: this._evmAddress,
1260
- });
1261
- }
1262
- }
1263
- catch (error) {
1264
- logger_1.logger.error("OnChainChanged: Failed to emit chain event:", error);
1265
- }
1266
- return [2 /*return*/];
1267
- });
1268
- });
1269
- };
1270
- FormoAnalytics.prototype.registerConnectListener = function (provider) {
1271
- var _this = this;
1272
- logger_1.logger.info("registerConnectListener");
1273
- var listener = function () {
1274
- var args = [];
1275
- for (var _i = 0; _i < arguments.length; _i++) {
1276
- args[_i] = arguments[_i];
1277
- }
1278
- var connection = args[0];
1279
- _this.onConnected(provider, connection);
1280
- };
1281
- provider.on("connect", listener);
1282
- this.addProviderListener(provider, "connect", listener);
1283
- };
1284
- FormoAnalytics.prototype.registerDisconnectListener = function (provider) {
1285
- var _this = this;
1286
- logger_1.logger.info("registerDisconnectListener");
1287
- var listener = function (_error) { return __awaiter(_this, void 0, void 0, function () {
1288
- var e_2;
1289
- return __generator(this, function (_a) {
1290
- switch (_a.label) {
1291
- case 0:
1292
- if (this._provider !== provider)
1293
- return [2 /*return*/];
1294
- logger_1.logger.info("OnDisconnect: Wallet disconnect event received, current state:", {
1295
- currentAddress: this._evmAddress,
1296
- currentChainId: this._evmChainId,
1297
- });
1298
- if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 5];
1299
- _a.label = 1;
1300
- case 1:
1301
- _a.trys.push([1, 3, , 4]);
1302
- // Pass current state explicitly to ensure we have the data for the disconnect event
1303
- return [4 /*yield*/, this.disconnect({
1304
- chainId: this._evmChainId,
1305
- address: this._evmAddress,
1306
- })];
1307
- case 2:
1308
- // Pass current state explicitly to ensure we have the data for the disconnect event
1309
- _a.sent();
1310
- return [3 /*break*/, 4];
1311
- case 3:
1312
- e_2 = _a.sent();
1313
- logger_1.logger.error("Error during disconnect in disconnect listener", e_2);
1314
- return [3 /*break*/, 4];
1315
- case 4: return [3 /*break*/, 6];
1316
- case 5:
1317
- logger_1.logger.debug("OnDisconnect: Disconnect event skipped (autocapture.disconnect: false)");
1318
- // Still clear state even if not tracking the event
1319
- this.clearChainState('evm');
1320
- _a.label = 6;
1321
- case 6: return [2 /*return*/];
1322
- }
1323
- });
1324
- }); };
1325
- provider.on("disconnect", listener);
1326
- this.addProviderListener(provider, "disconnect", listener);
1327
- };
1328
- FormoAnalytics.prototype.onConnected = function (provider, connection) {
1329
- return __awaiter(this, void 0, void 0, function () {
1330
- var chainId, address, wasDisconnected, isActiveProvider, providerInfo, effectiveChainId, providerInfo, e_3;
1331
- return __generator(this, function (_a) {
1332
- switch (_a.label) {
1333
- case 0:
1334
- logger_1.logger.info("onConnected", connection);
1335
- _a.label = 1;
1336
- case 1:
1337
- _a.trys.push([1, 3, , 4]);
1338
- if (!(connection === null || connection === void 0 ? void 0 : connection.chainId) || typeof connection.chainId !== "string")
1339
- return [2 /*return*/];
1340
- chainId = (0, chain_1.parseChainId)(connection.chainId);
1341
- return [4 /*yield*/, this.getAddress(provider)];
1342
- case 2:
1343
- address = _a.sent();
1344
- if (chainId && address) {
1345
- wasDisconnected = !this._evmAddress;
1346
- // Set provider if none exists
1347
- if (!this._provider) {
1348
- this._provider = provider;
1349
- }
1350
- isActiveProvider = this._provider === provider;
1351
- // Update state from active provider so disconnect events keep valid
1352
- // address/chainId values - except while suppressed, where we must not
1353
- // LEARN identity (only drop a stale EVM wallet on a switch).
1354
- if (isActiveProvider) {
1355
- if (this.isTrackingSuppressed()) {
1356
- this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
1357
- }
1358
- else {
1359
- this.setChainState('evm', {
1360
- chainId: chainId,
1361
- address: (0, address_1.validateAndChecksumAddress)(address) || undefined,
1362
- });
1363
- }
1364
- }
1365
- // Conditionally emit connect event based on tracking configuration
1366
- if (isActiveProvider && this._evmAddress) {
1367
- providerInfo = this.getProviderInfo(provider);
1368
- effectiveChainId = chainId || 0;
1369
- if (this.isAutocaptureEnabled("connect")) {
1370
- logger_1.logger.info("OnConnected: Detected wallet connection, emitting connect event", {
1371
- chainId: chainId,
1372
- wasDisconnected: wasDisconnected,
1373
- providerName: providerInfo.name,
1374
- rdns: providerInfo.rdns,
1375
- hasChainId: !!chainId,
1376
- isActiveProvider: isActiveProvider,
1377
- });
1378
- if (effectiveChainId === 0) {
1379
- logger_1.logger.info("OnConnected: Using fallback chainId 0 for connect event");
1380
- }
1381
- this.connect({
1382
- chainId: effectiveChainId,
1383
- address: address,
1384
- }, {
1385
- providerName: providerInfo.name,
1386
- rdns: providerInfo.rdns,
1387
- }).catch(function (error) {
1388
- logger_1.logger.error("Failed to track connect event during provider connection:", error);
1389
- });
1390
- }
1391
- else {
1392
- logger_1.logger.debug("OnConnected: Connect event skipped (autocapture.connect: false)", {
1393
- chainId: chainId,
1394
- address: address,
1395
- providerName: providerInfo.name,
1396
- });
1397
- }
1398
- }
1399
- else if (address && !isActiveProvider) {
1400
- providerInfo = this.getProviderInfo(provider);
1401
- logger_1.logger.debug("OnConnected: Skipping connect event for non-active provider", {
1402
- chainId: chainId,
1403
- providerName: providerInfo.name,
1404
- rdns: providerInfo.rdns,
1405
- isActiveProvider: isActiveProvider,
1406
- activeProviderInfo: this._provider
1407
- ? this.getProviderInfo(this._provider)
1408
- : null,
1409
- });
1410
- }
1411
- }
1412
- return [3 /*break*/, 4];
1413
- case 3:
1414
- e_3 = _a.sent();
1415
- logger_1.logger.error("Error handling connect event", e_3);
1416
- return [3 /*break*/, 4];
1417
- case 4: return [2 /*return*/];
1418
- }
1419
- });
1420
- });
1421
- };
1422
- FormoAnalytics.prototype.registerRequestListeners = function (provider) {
1423
- var _this = this;
1424
- logger_1.logger.info("registerRequestListeners");
1425
- if (!provider) {
1426
- logger_1.logger.error("Provider not found for request (signature, transaction) tracking");
1427
- return;
1428
- }
1429
- // Check if the provider is already wrapped with our SDK's wrapper
1430
- var currentRequest = provider.request;
1431
- if (this.isProviderAlreadyWrapped(provider, currentRequest)) {
1432
- logger_1.logger.info("Provider already wrapped with our SDK; skipping request wrapping.");
1433
- return;
1434
- }
1435
- var request = provider.request.bind(provider);
1436
- var wrappedRequest = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
1437
- var capturedChainId_1, _c, response_1, error_3, rpcError, transactionHash_1, error_4, rpcError;
1438
- var _this = this;
1439
- var method = _b.method, params = _b.params;
1440
- return __generator(this, function (_d) {
1441
- switch (_d.label) {
1442
- case 0:
1443
- if (!(Array.isArray(params) &&
1444
- ["eth_signTypedData_v4", "personal_sign"].includes(method))) return [3 /*break*/, 6];
1445
- if (!this.isAutocaptureEnabled("signature")) {
1446
- logger_1.logger.debug("Signature event skipped (autocapture.signature: false)", { method: method });
1447
- return [2 /*return*/, request({ method: method, params: params })];
1448
- }
1449
- _c = this._evmChainId;
1450
- if (_c) return [3 /*break*/, 2];
1451
- return [4 /*yield*/, this.getCurrentChainId(provider)];
1452
- case 1:
1453
- _c = (_d.sent());
1454
- _d.label = 2;
1455
- case 2:
1456
- capturedChainId_1 = _c;
1457
- // Fire-and-forget tracking
1458
- (function () { return __awaiter(_this, void 0, void 0, function () {
1459
- return __generator(this, function (_a) {
1460
- try {
1461
- this.signature(__assign({ status: types_1.SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1)));
1462
- }
1463
- catch (e) {
1464
- logger_1.logger.error("Formo: Failed to track signature request", e);
1465
- }
1466
- return [2 /*return*/];
1467
- });
1468
- }); })();
1469
- _d.label = 3;
1470
- case 3:
1471
- _d.trys.push([3, 5, , 6]);
1472
- return [4 /*yield*/, request({ method: method, params: params })];
1473
- case 4:
1474
- response_1 = (_d.sent());
1475
- // Track signature confirmation only for truthy responses
1476
- if (response_1) {
1477
- (function () { return __awaiter(_this, void 0, void 0, function () {
1478
- return __generator(this, function (_a) {
1479
- try {
1480
- this.signature(__assign({ status: types_1.SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1)));
1481
- }
1482
- catch (e) {
1483
- logger_1.logger.error("Formo: Failed to track signature confirmation", e);
1484
- }
1485
- return [2 /*return*/];
1486
- });
1487
- }); })();
1488
- }
1489
- return [2 /*return*/, response_1];
1490
- case 5:
1491
- error_3 = _d.sent();
1492
- rpcError = error_3;
1493
- if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
1494
- // Use the already cast rpcError to avoid duplication
1495
- (function () { return __awaiter(_this, void 0, void 0, function () {
1496
- return __generator(this, function (_a) {
1497
- try {
1498
- this.signature(__assign({ status: types_1.SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1)));
1499
- }
1500
- catch (e) {
1501
- logger_1.logger.error("Formo: Failed to track signature rejection", e);
1502
- }
1503
- return [2 /*return*/];
1504
- });
1505
- }); })();
1506
- }
1507
- throw error_3;
1508
- case 6:
1509
- if (!(Array.isArray(params) &&
1510
- method === "eth_sendTransaction" &&
1511
- params[0])) return [3 /*break*/, 10];
1512
- if (!this.isAutocaptureEnabled("transaction")) {
1513
- logger_1.logger.debug("Transaction event skipped (autocapture.transaction: false)", { method: method });
1514
- return [2 /*return*/, request({ method: method, params: params })];
1515
- }
1516
- (function () { return __awaiter(_this, void 0, void 0, function () {
1517
- var payload, e_4;
1518
- return __generator(this, function (_a) {
1519
- switch (_a.label) {
1520
- case 0:
1521
- _a.trys.push([0, 2, , 3]);
1522
- return [4 /*yield*/, this.buildTransactionEventPayload(params, provider)];
1523
- case 1:
1524
- payload = _a.sent();
1525
- this.transaction(__assign({ status: types_1.TransactionStatus.STARTED }, payload));
1526
- return [3 /*break*/, 3];
1527
- case 2:
1528
- e_4 = _a.sent();
1529
- logger_1.logger.error("Formo: Failed to track transaction start", e_4);
1530
- return [3 /*break*/, 3];
1531
- case 3: return [2 /*return*/];
1532
- }
1533
- });
1534
- }); })();
1535
- _d.label = 7;
1536
- case 7:
1537
- _d.trys.push([7, 9, , 10]);
1538
- return [4 /*yield*/, request({
1539
- method: method,
1540
- params: params,
1541
- })];
1542
- case 8:
1543
- transactionHash_1 = (_d.sent());
1544
- (function () { return __awaiter(_this, void 0, void 0, function () {
1545
- var payload, e_5;
1546
- return __generator(this, function (_a) {
1547
- switch (_a.label) {
1548
- case 0:
1549
- _a.trys.push([0, 2, , 3]);
1550
- return [4 /*yield*/, this.buildTransactionEventPayload(params, provider)];
1551
- case 1:
1552
- payload = _a.sent();
1553
- this.transaction(__assign(__assign({ status: types_1.TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }));
1554
- // Start async polling for transaction receipt
1555
- this.pollTransactionReceipt(provider, transactionHash_1, payload);
1556
- return [3 /*break*/, 3];
1557
- case 2:
1558
- e_5 = _a.sent();
1559
- logger_1.logger.error("Formo: Failed to track transaction broadcast", e_5);
1560
- return [3 /*break*/, 3];
1561
- case 3: return [2 /*return*/];
1562
- }
1563
- });
1564
- }); })();
1565
- return [2 /*return*/, transactionHash_1];
1566
- case 9:
1567
- error_4 = _d.sent();
1568
- rpcError = error_4;
1569
- if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
1570
- // Use the already cast rpcError to avoid duplication
1571
- (function () { return __awaiter(_this, void 0, void 0, function () {
1572
- var payload, e_6;
1573
- return __generator(this, function (_a) {
1574
- switch (_a.label) {
1575
- case 0:
1576
- _a.trys.push([0, 2, , 3]);
1577
- return [4 /*yield*/, this.buildTransactionEventPayload(params, provider)];
1578
- case 1:
1579
- payload = _a.sent();
1580
- this.transaction(__assign({ status: types_1.TransactionStatus.REJECTED }, payload));
1581
- return [3 /*break*/, 3];
1582
- case 2:
1583
- e_6 = _a.sent();
1584
- logger_1.logger.error("Formo: Failed to track transaction rejection", e_6);
1585
- return [3 /*break*/, 3];
1586
- case 3: return [2 /*return*/];
1587
- }
1588
- });
1589
- }); })();
1590
- }
1591
- throw error_4;
1592
- case 10: return [2 /*return*/, request({ method: method, params: params })];
1593
- }
1594
- });
1595
- }); };
1596
- // Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
1597
- wrappedRequest[types_1.WRAPPED_REQUEST_SYMBOL] = true;
1598
- provider[types_1.WRAPPED_REQUEST_REF_SYMBOL] =
1599
- wrappedRequest;
1600
- try {
1601
- // Attempt to assign the wrapped request function (rely on try-catch for mutability errors)
1602
- provider.request = wrappedRequest;
1603
- }
1604
- catch (e) {
1605
- logger_1.logger.warn("Failed to wrap provider.request; skipping", e);
1606
- }
1607
- };
1608
- FormoAnalytics.prototype.onLocationChange = function () {
1609
- return __awaiter(this, void 0, void 0, function () {
1610
- return __generator(this, function (_a) {
1611
- if (this._currentUrl !== window.location.href) {
1612
- this._currentUrl = window.location.href;
1613
- this.trackPageHit();
1614
- }
1615
- return [2 /*return*/];
1616
- });
1617
- });
1618
- };
1619
- FormoAnalytics.prototype.trackPageHits = function () {
1620
- var _this = this;
1621
- // Install a single, instance-agnostic wrapper around history.pushState /
1622
- // replaceState so concurrent SDK instances (React Strict Mode, HMR) don't
1623
- // each stack their own wrapper - which would dispatch N synthetic events
1624
- // per navigation and produce O(N^2) onLocationChange calls. The wrapper
1625
- // dispatches once; per-instance bookkeeping is done by per-instance
1626
- // listeners that each register/unregister themselves.
1627
- FormoAnalytics.installHistoryHooksOnce();
1628
- this._onPopStateListener = function () { return _this.onLocationChange(); };
1629
- this._onLocationChangeListener = function () { return _this.onLocationChange(); };
1630
- window.addEventListener("popstate", this._onPopStateListener);
1631
- window.addEventListener("locationchange", this._onLocationChangeListener);
1632
- };
1633
- /**
1634
- * Wrap history.pushState / replaceState exactly once per `history` object,
1635
- * regardless of how many SDK instances are constructed. Uses a Symbol
1636
- * marker so we recognize our own wrapper across module reloads in HMR.
1637
- */
1638
- FormoAnalytics.installHistoryHooksOnce = function () {
1639
- if (typeof history === "undefined" || typeof window === "undefined")
1640
- return;
1641
- var marker = Symbol.for("formo.historyWrapped");
1642
- if (history[marker])
1643
- return;
1644
- history[marker] = true;
1645
- var dispatch = function () { return window.dispatchEvent(new window.Event("locationchange")); };
1646
- var oldPushState = history.pushState;
1647
- history.pushState = function pushState() {
1648
- var args = [];
1649
- for (var _i = 0; _i < arguments.length; _i++) {
1650
- args[_i] = arguments[_i];
1651
- }
1652
- var ret = oldPushState.apply(this, args);
1653
- dispatch();
1654
- return ret;
1655
- };
1656
- var oldReplaceState = history.replaceState;
1657
- history.replaceState = function replaceState() {
1658
- var args = [];
1659
- for (var _i = 0; _i < arguments.length; _i++) {
1660
- args[_i] = arguments[_i];
1661
- }
1662
- var ret = oldReplaceState.apply(this, args);
1663
- dispatch();
1664
- return ret;
1665
- };
1666
- };
1667
- FormoAnalytics.prototype.trackPageHit = function (category, name, properties, context, callback) {
1668
- return __awaiter(this, void 0, void 0, function () {
1669
- var _this = this;
1670
- return __generator(this, function (_a) {
1671
- if (!this.shouldTrack()) {
1672
- logger_1.logger.info("Track page hit: Skipping event due to tracking configuration");
1673
- return [2 /*return*/];
1674
- }
1675
- setTimeout(function () {
1676
- // Drop in-flight page hits from an SDK instance that was torn down
1677
- // between scheduling and firing (e.g. provider remount in React Strict
1678
- // Mode / HMR). Otherwise the orphan instance would queue a page event
1679
- // here with its stale, never-populated `currentAddress`.
1680
- if (_this._pageHooksDisposed)
1681
- return;
1682
- (function () { return __awaiter(_this, void 0, void 0, function () {
1683
- var e_7;
1684
- return __generator(this, function (_a) {
1685
- switch (_a.label) {
1686
- case 0:
1687
- _a.trys.push([0, 2, , 3]);
1688
- return [4 /*yield*/, this.trackEvent(constants_1.EventType.PAGE, {
1689
- category: category,
1690
- name: name,
1691
- }, properties, context, callback)];
1692
- case 1:
1693
- _a.sent();
1694
- return [3 /*break*/, 3];
1695
- case 2:
1696
- e_7 = _a.sent();
1697
- logger_1.logger.error("Formo: Failed to track page hit", e_7);
1698
- return [3 /*break*/, 3];
1699
- case 3: return [2 /*return*/];
1700
- }
1701
- });
1702
- }); })();
1703
- }, 300);
1704
- return [2 /*return*/];
1705
- });
1706
- });
1707
- };
1708
- FormoAnalytics.prototype.trackEvent = function (type, payload, properties, context, callback) {
1709
- return __awaiter(this, void 0, void 0, function () {
1710
- var error_5;
1711
- return __generator(this, function (_a) {
1712
- switch (_a.label) {
1713
- case 0:
1714
- _a.trys.push([0, 2, , 3]);
1715
- if (!this.shouldTrack()) {
1716
- logger_1.logger.info("Skipping ".concat(type, " event due to tracking configuration"));
1717
- return [2 /*return*/];
1718
- }
1719
- return [4 /*yield*/, this.eventManager.addEvent(__assign(__assign({ type: type }, payload), { properties: properties, context: context, callback: callback }), this.currentAddress, this.currentUserId)];
1720
- case 1:
1721
- _a.sent();
1722
- return [3 /*break*/, 3];
1723
- case 2:
1724
- error_5 = _a.sent();
1725
- logger_1.logger.error("Error tracking event:", error_5);
1726
- return [3 /*break*/, 3];
1727
- case 3: return [2 /*return*/];
1728
- }
1729
- });
1730
- });
1731
- };
1732
- /**
1733
- * Visitor-level tracking suppression.
1734
- *
1735
- * Returns true when the SDK must not persist any identity/session/chain
1736
- * state or send any events for this visitor - i.e. an explicit opt-out or a
1737
- * jurisdiction/timezone exclusion. Public entry points that write state
1738
- * before reaching the `shouldTrack()` event gate (identify/connect/detect)
1739
- * check this first so suppressed visitors leave no cookies or session state.
1740
- * @returns {boolean} True if all tracking and persistence must be suppressed
1741
- * @internal Also read by `identifyPrivyUser` (via a structural cast) so the
1742
- * Privy sync skips chain reconciliation and emission for suppressed visitors.
1743
- */
1744
- FormoAnalytics.prototype.isTrackingSuppressed = function () {
1745
- return this.hasOptedOutTracking() || this.isCurrentEnvironmentExcluded();
1746
- };
1747
- /**
1748
- * Whether the current chain id is in `tracking.excludeChains`.
1749
- *
1750
- * Split out from `shouldTrack()` so `identify()` can check it *before*
1751
- * mutating identity state. `trackEvent()` drops an excluded event silently
1752
- * and returns void, but `identify()` marks the wallet as identified first, so
1753
- * without this guard an identify on an excluded chain is dedup-marked and
1754
- * then discarded, and the wallet never re-emits for the rest of the session
1755
- * even after switching to an allowed chain. On the Privy path that loses the
1756
- * user's whole cluster at once rather than a single wallet.
1757
- *
1758
- * Mirrors the chain rule in `shouldTrack()`: only applies when `tracking` is
1759
- * an options object with `excludeChains` set, and only once a chain id is
1760
- * known.
1761
- */
1762
- FormoAnalytics.prototype.isCurrentChainExcluded = function () {
1763
- if (this.options.tracking === null ||
1764
- typeof this.options.tracking !== "object" ||
1765
- Array.isArray(this.options.tracking)) {
1766
- return false;
1767
- }
1768
- var _a = this.options.tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
1769
- return (excludeChains.length > 0 &&
1770
- !!this.currentChainId &&
1771
- excludeChains.includes(this.currentChainId));
1772
- };
1773
- /**
1774
- * Whether the current environment is excluded from tracking - the visitor's
1775
- * timezone, the current hostname, or the current pathname matches a
1776
- * configured exclusion.
1777
- *
1778
- * Timezone is visitor/session-level (stable for the session); host/path are
1779
- * current-page-level and transient - if a SPA navigates to an allowed path,
1780
- * tracking resumes for future actions. Used as the "do not write identity or
1781
- * send events" gate at every entry point that would persist state before the
1782
- * `shouldTrack()` event gate.
1783
- * @returns {boolean} True if the current environment is excluded
1784
- */
1785
- FormoAnalytics.prototype.isCurrentEnvironmentExcluded = function () {
1786
- return (this.isTimezoneExcluded() ||
1787
- this.isHostExcluded() ||
1788
- this.isPathExcluded());
1789
- };
1790
- /**
1791
- * Whether the current hostname matches a configured `tracking.excludeHosts`
1792
- * entry (exact match). Current-page-level - see isCurrentEnvironmentExcluded.
1793
- * @returns {boolean} True if the current hostname is excluded
1794
- */
1795
- FormoAnalytics.prototype.isHostExcluded = function () {
1796
- var tracking = this.options.tracking;
1797
- if (tracking === null ||
1798
- typeof tracking !== "object" ||
1799
- Array.isArray(tracking)) {
1800
- return false;
1801
- }
1802
- if (typeof window === "undefined") {
1803
- return false;
1804
- }
1805
- var _a = tracking.excludeHosts, excludeHosts = _a === void 0 ? [] : _a;
1806
- return excludeHosts.includes(window.location.hostname);
1807
- };
1808
- /**
1809
- * Whether the current pathname matches a configured `tracking.excludePaths`
1810
- * entry (exact match). Current-page-level - see isCurrentEnvironmentExcluded.
1811
- * @returns {boolean} True if the current pathname is excluded
1812
- */
1813
- FormoAnalytics.prototype.isPathExcluded = function () {
1814
- var tracking = this.options.tracking;
1815
- if (tracking === null ||
1816
- typeof tracking !== "object" ||
1817
- Array.isArray(tracking)) {
1818
- return false;
1819
- }
1820
- if (typeof window === "undefined") {
1821
- return false;
1822
- }
1823
- var _a = tracking.excludePaths, excludePaths = _a === void 0 ? [] : _a;
1824
- return excludePaths.includes(window.location.pathname);
1825
- };
1826
- /**
1827
- * Whether the current call is in a visitor-level suppression state - opt-out
1828
- * or excluded timezone - for which any persisted identity cookie should be
1829
- * actively purged (not merely skipped). Host/path exclusions are
1830
- * deliberately excluded here: they are transient current-page states, so a
1831
- * cookie legitimately written on an allowed page must survive a visit to an
1832
- * excluded route.
1833
- * @returns {boolean} True if persisted identity must be purged
1834
- */
1835
- FormoAnalytics.prototype.isPersistedIdentityPurgeRequired = function () {
1836
- return this.hasOptedOutTracking() || this.isTimezoneExcluded();
1837
- };
1838
- /**
1839
- * Whether the visitor's browser-resolved timezone matches a configured
1840
- * `tracking.excludeTimezones` entry (case-insensitive). Client-side and
1841
- * best-effort - see TrackingOptions.excludeTimezones.
1842
- * @returns {boolean} True if the current timezone is excluded
1843
- */
1844
- FormoAnalytics.prototype.isTimezoneExcluded = function () {
1845
- var tracking = this.options.tracking;
1846
- if (tracking === null ||
1847
- typeof tracking !== "object" ||
1848
- Array.isArray(tracking)) {
1849
- return false;
1850
- }
1851
- var _a = tracking.excludeTimezones, excludeTimezones = _a === void 0 ? [] : _a;
1852
- if (excludeTimezones.length === 0) {
1853
- return false;
1854
- }
1855
- var timezone = (0, timezone_1.getTimezone)();
1856
- if (!timezone) {
1857
- return false;
1858
- }
1859
- var lowerTimezone = timezone.toLowerCase();
1860
- return excludeTimezones.some(function (tz) { return typeof tz === "string" && tz.toLowerCase() === lowerTimezone; });
1861
- };
1862
- /**
1863
- * Determines if tracking should be enabled based on configuration and consent
1864
- * @returns {boolean} True if tracking should be enabled
1865
- */
1866
- FormoAnalytics.prototype.shouldTrack = function () {
1867
- // First check if user has opted out of tracking
1868
- if (this.hasOptedOutTracking()) {
1869
- return false;
1870
- }
1871
- // Check if tracking is explicitly provided as a boolean
1872
- if (typeof this.options.tracking === "boolean") {
1873
- return this.options.tracking;
1874
- }
1875
- // Handle object configuration with exclusion rules
1876
- if (this.options.tracking !== null &&
1877
- typeof this.options.tracking === "object" &&
1878
- !Array.isArray(this.options.tracking)) {
1879
- var _a = this.options.tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
1880
- // Environment exclusions (timezone / host / path) - no identify / connect
1881
- // / track events while excluded. Host/path are exact-match.
1882
- if (this.isCurrentEnvironmentExcluded()) {
1883
- return false;
1884
- }
1885
- // Check chainId exclusions
1886
- if (excludeChains.length > 0 &&
1887
- this.currentChainId &&
1888
- excludeChains.includes(this.currentChainId)) {
1889
- return false;
1890
- }
1891
- // If nothing is excluded, tracking is enabled
1892
- return true;
1893
- }
1894
- // Default behavior: track everywhere except localhost
1895
- return !(0, validators_1.isLocalhost)();
1896
- };
1897
- /**
1898
- * Check if a specific wallet event type is enabled for autocapture
1899
- * @param eventType The wallet event type to check
1900
- * @returns {boolean} True if the event type should be autocaptured
1901
- */
1902
- FormoAnalytics.prototype.isAutocaptureEnabled = function (eventType) {
1903
- // If no configuration provided, default to enabled
1904
- if (this.options.autocapture === undefined) {
1905
- return true;
1906
- }
1907
- // If boolean, return that value for all events
1908
- if (typeof this.options.autocapture === "boolean") {
1909
- return this.options.autocapture;
1910
- }
1911
- // If it's an object, check the specific event configuration
1912
- if (this.options.autocapture !== null &&
1913
- typeof this.options.autocapture === "object") {
1914
- var eventConfig = this.options.autocapture[eventType];
1915
- // Default to true if not explicitly set to false
1916
- return eventConfig !== false;
1917
- }
1918
- // Default to enabled if no specific configuration
1919
- return true;
1920
- };
1921
- /*
1922
- Utility functions
1923
- */
1924
- /**
1925
- * Get provider information for a given provider
1926
- * @param provider The provider to get info for
1927
- * @returns Provider information
1928
- */
1929
- FormoAnalytics.prototype.getProviderInfo = function (provider) {
1930
- // First check if provider is in our EIP-6963 providers list
1931
- var eip6963Provider = this._providers.find(function (p) { return p.provider === provider; });
1932
- if (eip6963Provider) {
1933
- return {
1934
- name: eip6963Provider.info.name,
1935
- rdns: eip6963Provider.info.rdns,
1936
- };
1937
- }
1938
- // Fallback to injected provider detection
1939
- var injectedInfo = (0, provider_1.detectInjectedProviderInfo)(provider);
1940
- return {
1941
- name: injectedInfo.name,
1942
- rdns: injectedInfo.rdns,
1943
- };
1944
- };
1945
- FormoAnalytics.prototype.getProviders = function () {
1946
- return __awaiter(this, void 0, void 0, function () {
1947
- var store, providers, injected_1, injectedProviderInfo, injectedDetail, uniqueProviders, _i, uniqueProviders_1, detail;
1948
- var _this = this;
1949
- return __generator(this, function (_a) {
1950
- store = (0, mipd_1.createStore)();
1951
- providers = store.getProviders();
1952
- store.subscribe(function (providerDetails) {
1953
- providers = providerDetails;
1954
- // Process newly added providers with proper deduplication
1955
- var newlyAddedDetails = providerDetails.filter(function (detail) {
1956
- var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
1957
- return provider && !_this._seenProviders.has(provider);
1958
- });
1959
- // Add new providers to the array without overwriting existing ones
1960
- for (var _i = 0, newlyAddedDetails_1 = newlyAddedDetails; _i < newlyAddedDetails_1.length; _i++) {
1961
- var detail = newlyAddedDetails_1[_i];
1962
- _this.safeAddProviderDetail(detail);
1963
- }
1964
- // Track listeners for newly discovered providers only
1965
- var newDetails = providerDetails.filter(function (detail) {
1966
- var p = detail === null || detail === void 0 ? void 0 : detail.provider;
1967
- return !!p && !_this._trackedProviders.has(p);
1968
- });
1969
- if (newDetails.length > 0) {
1970
- _this.trackProviders(newDetails);
1971
- // Detect newly discovered wallets (session de-dupes) with error handling
1972
- (function () { return __awaiter(_this, void 0, void 0, function () {
1973
- var e_8;
1974
- return __generator(this, function (_a) {
1975
- switch (_a.label) {
1976
- case 0:
1977
- _a.trys.push([0, 2, , 3]);
1978
- return [4 /*yield*/, this.detectWallets(newDetails)];
1979
- case 1:
1980
- _a.sent();
1981
- return [3 /*break*/, 3];
1982
- case 2:
1983
- e_8 = _a.sent();
1984
- logger_1.logger.error("Formo: Failed to detect wallets", e_8);
1985
- return [3 /*break*/, 3];
1986
- case 3: return [2 /*return*/];
1987
- }
1988
- });
1989
- }); })();
1990
- }
1991
- // Clean up providers that are no longer available
1992
- _this.cleanupUnavailableProviders();
1993
- });
1994
- // Fallback to injected provider if no providers are found
1995
- if (providers.length === 0) {
1996
- injected_1 = typeof window !== "undefined" ? window.ethereum : undefined;
1997
- if (injected_1) {
1998
- // If we have already detected and cached the injected provider, and it's the same instance, return the cached result
1999
- if (this._injectedProviderDetail &&
2000
- this._injectedProviderDetail.provider === injected_1) {
2001
- // Ensure it's tracked
2002
- if (!this._trackedProviders.has(injected_1)) {
2003
- this.trackEIP1193Provider(injected_1);
2004
- }
2005
- // Merge with existing providers instead of overwriting
2006
- if (!this._providers.some(function (existing) { return existing.provider === injected_1; })) {
2007
- this._providers = __spreadArray(__spreadArray([], this._providers, true), [
2008
- this._injectedProviderDetail,
2009
- ], false);
2010
- }
2011
- return [2 /*return*/, this._providers];
2012
- }
2013
- // Re-check if the injected provider is already tracked just before tracking
2014
- if (!this._trackedProviders.has(injected_1)) {
2015
- this.trackEIP1193Provider(injected_1);
2016
- }
2017
- injectedProviderInfo = (0, provider_1.detectInjectedProviderInfo)(injected_1);
2018
- injectedDetail = {
2019
- provider: injected_1,
2020
- info: injectedProviderInfo,
2021
- };
2022
- // Cache the detected injected provider detail
2023
- this._injectedProviderDetail = injectedDetail;
2024
- // Merge with existing providers instead of overwriting
2025
- this.safeAddProviderDetail(injectedDetail);
2026
- }
2027
- return [2 /*return*/, this._providers];
2028
- }
2029
- uniqueProviders = providers.filter(function (detail) {
2030
- var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
2031
- return provider && !_this._seenProviders.has(provider);
2032
- });
2033
- // Add to seen providers and instances, ensuring no duplicates in _providers
2034
- for (_i = 0, uniqueProviders_1 = uniqueProviders; _i < uniqueProviders_1.length; _i++) {
2035
- detail = uniqueProviders_1[_i];
2036
- this.safeAddProviderDetail(detail);
2037
- }
2038
- return [2 /*return*/, this._providers];
2039
- });
2040
- });
2041
- };
2042
- Object.defineProperty(FormoAnalytics.prototype, "providers", {
2043
- get: function () {
2044
- return this._providers;
2045
- },
2046
- enumerable: false,
2047
- configurable: true
2048
- });
2049
- FormoAnalytics.prototype.detectWallets = function (providers) {
2050
- return __awaiter(this, void 0, void 0, function () {
2051
- var _i, providers_2, eip6963ProviderDetail, err_2;
2052
- return __generator(this, function (_a) {
2053
- switch (_a.label) {
2054
- case 0:
2055
- _a.trys.push([0, 5, , 6]);
2056
- _i = 0, providers_2 = providers;
2057
- _a.label = 1;
2058
- case 1:
2059
- if (!(_i < providers_2.length)) return [3 /*break*/, 4];
2060
- eip6963ProviderDetail = providers_2[_i];
2061
- return [4 /*yield*/, this.detect({
2062
- providerName: eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.info.name,
2063
- rdns: eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.info.rdns,
2064
- })];
2065
- case 2:
2066
- _a.sent();
2067
- _a.label = 3;
2068
- case 3:
2069
- _i++;
2070
- return [3 /*break*/, 1];
2071
- case 4: return [3 /*break*/, 6];
2072
- case 5:
2073
- err_2 = _a.sent();
2074
- logger_1.logger.error("Error detect all wallets:", err_2);
2075
- return [3 /*break*/, 6];
2076
- case 6: return [2 /*return*/];
2077
- }
2078
- });
2079
- });
2080
- };
2081
- Object.defineProperty(FormoAnalytics.prototype, "provider", {
2082
- get: function () {
2083
- return this._provider;
2084
- },
2085
- enumerable: false,
2086
- configurable: true
2087
- });
2088
- Object.defineProperty(FormoAnalytics.prototype, "solana", {
2089
- /**
2090
- * Access the Solana integration manager.
2091
- * Lazily creates one if not already initialized.
2092
- *
2093
- * @example
2094
- * ```tsx
2095
- * formo.solana.setStore(client.store);
2096
- * formo.solana.setCluster("devnet");
2097
- * // For signatures, use formo.signature() directly
2098
- * ```
2099
- */
2100
- get: function () {
2101
- if (!this.solanaManager) {
2102
- this.solanaManager = new SolanaManager_1.SolanaManager(this);
2103
- }
2104
- return this.solanaManager;
2105
- },
2106
- enumerable: false,
2107
- configurable: true
2108
- });
2109
- FormoAnalytics.prototype.getAddress = function (provider) {
2110
- return __awaiter(this, void 0, void 0, function () {
2111
- var p, accounts, err_3, code;
2112
- return __generator(this, function (_a) {
2113
- switch (_a.label) {
2114
- case 0:
2115
- // Use EVM-specific state to avoid returning a Solana address in an EVM context
2116
- if (this._chainState.evm.address)
2117
- return [2 /*return*/, this._chainState.evm.address];
2118
- p = provider || this.provider;
2119
- if (!p) {
2120
- logger_1.logger.info("The provider is not set");
2121
- return [2 /*return*/, null];
2122
- }
2123
- _a.label = 1;
2124
- case 1:
2125
- _a.trys.push([1, 3, , 4]);
2126
- return [4 /*yield*/, this.getAccounts(p)];
2127
- case 2:
2128
- accounts = _a.sent();
2129
- if (accounts && accounts.length > 0) {
2130
- return [2 /*return*/, (0, address_1.validateAndChecksumAddress)(accounts[0]) || null];
2131
- }
2132
- return [3 /*break*/, 4];
2133
- case 3:
2134
- err_3 = _a.sent();
2135
- code = err_3 === null || err_3 === void 0 ? void 0 : err_3.code;
2136
- if (code !== 4001) {
2137
- logger_1.logger.error("FormoAnalytics::getAccounts: eth_accounts threw an error", err_3);
2138
- }
2139
- return [2 /*return*/, null];
2140
- case 4: return [2 /*return*/, null];
2141
- }
2142
- });
2143
- });
2144
- };
2145
- FormoAnalytics.prototype.getAccounts = function (provider) {
2146
- return __awaiter(this, void 0, void 0, function () {
2147
- var p, res, err_4, code;
2148
- return __generator(this, function (_a) {
2149
- switch (_a.label) {
2150
- case 0:
2151
- p = provider || this.provider;
2152
- _a.label = 1;
2153
- case 1:
2154
- _a.trys.push([1, 3, , 4]);
2155
- return [4 /*yield*/, (p === null || p === void 0 ? void 0 : p.request({
2156
- method: "eth_accounts",
2157
- }))];
2158
- case 2:
2159
- res = _a.sent();
2160
- if (!res || res.length === 0)
2161
- return [2 /*return*/, null];
2162
- return [2 /*return*/, res
2163
- .map(function (e) { return (0, address_1.validateAndChecksumAddress)(e); })
2164
- .filter(function (e) { return e !== undefined; })];
2165
- case 3:
2166
- err_4 = _a.sent();
2167
- code = err_4 === null || err_4 === void 0 ? void 0 : err_4.code;
2168
- if (code !== 4001) {
2169
- logger_1.logger.error("FormoAnalytics::getAccounts: eth_accounts threw an error", err_4);
2170
- }
2171
- return [2 /*return*/, null];
2172
- case 4: return [2 /*return*/];
2173
- }
2174
- });
2175
- });
2176
- };
2177
- FormoAnalytics.prototype.getCurrentChainId = function (provider) {
2178
- return __awaiter(this, void 0, void 0, function () {
2179
- var p, chainIdHex, err_5;
1245
+ var p, chainIdHex, err_2;
2180
1246
  return __generator(this, function (_a) {
2181
1247
  switch (_a.label) {
2182
1248
  case 0:
@@ -2199,500 +1265,26 @@ var FormoAnalytics = /** @class */ (function () {
2199
1265
  }
2200
1266
  return [2 /*return*/, (0, chain_1.parseChainId)(chainIdHex)];
2201
1267
  case 3:
2202
- err_5 = _a.sent();
2203
- logger_1.logger.error("eth_chainId threw an error:", err_5);
1268
+ err_2 = _a.sent();
1269
+ logger_1.logger.error("eth_chainId threw an error:", err_2);
2204
1270
  return [2 /*return*/, 0];
2205
1271
  case 4: return [2 /*return*/];
2206
1272
  }
2207
1273
  });
2208
1274
  });
2209
1275
  };
2210
- FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params,
2211
- // Intentionally not read. Kept for positional call-site arity.
2212
- _response, chainId) {
2213
- var _a;
2214
- var rawAddress = method === "personal_sign"
2215
- ? params[1]
2216
- : params[0];
2217
- var validAddress = (0, address_1.validateAndChecksumAddress)(rawAddress);
2218
- if (!validAddress) {
2219
- throw new Error("Invalid address in signature payload: ".concat(rawAddress));
2220
- }
2221
- var effectiveChainId = (_a = chainId !== null && chainId !== void 0 ? chainId : this._evmChainId) !== null && _a !== void 0 ? _a : undefined;
2222
- this.backfillActiveWallet(validAddress, effectiveChainId);
2223
- var basePayload = {
2224
- chainId: effectiveChainId,
2225
- address: validAddress,
2226
- };
2227
- if (method === "personal_sign") {
2228
- var message = Buffer.from(params[0].slice(2), "hex").toString("utf8");
2229
- return __assign(__assign({}, basePayload), { message: message });
2230
- }
2231
- // eth_signTypedData*: params[1] is the full EIP-712 struct.
2232
- return __assign(__assign({}, basePayload), { message: params[1] });
2233
- };
2234
- FormoAnalytics.prototype.buildTransactionEventPayload = function (params, provider) {
2235
- return __awaiter(this, void 0, void 0, function () {
2236
- var _a, data, from, to, value, validAddress, chainId, _b;
2237
- return __generator(this, function (_c) {
2238
- switch (_c.label) {
2239
- case 0:
2240
- _a = params[0], data = _a.data, from = _a.from, to = _a.to, value = _a.value;
2241
- validAddress = (0, address_1.validateAndChecksumAddress)(from);
2242
- if (!validAddress) {
2243
- throw new Error("Invalid address in transaction payload: ".concat(from));
2244
- }
2245
- _b = this._evmChainId;
2246
- if (_b) return [3 /*break*/, 2];
2247
- return [4 /*yield*/, this.getCurrentChainId(provider)];
2248
- case 1:
2249
- _b = (_c.sent());
2250
- _c.label = 2;
2251
- case 2:
2252
- chainId = _b;
2253
- this.backfillActiveWallet(validAddress, chainId);
2254
- return [2 /*return*/, {
2255
- chainId: chainId,
2256
- data: data,
2257
- address: validAddress,
2258
- to: to,
2259
- value: value,
2260
- }];
2261
- }
2262
- });
2263
- });
2264
- };
2265
- /**
2266
- * Persist an EVM address discovered through autocapture (signature / transaction)
2267
- * as the current EVM address when none is currently set. This lets subsequent
2268
- * track()/page() calls carry the address even when the underlying wallet never
2269
- * fires an EIP-1193 `accountsChanged` event (embedded wallets, smart accounts,
2270
- * social-login wrappers). If `accountsChanged` later fires it overwrites this
2271
- * value in the normal way; existing connections are never clobbered.
2272
- */
2273
- FormoAnalytics.prototype.backfillActiveWallet = function (address, chainId) {
2274
- // Never learn identity while suppressed (opt-out / timezone / excluded host
2275
- // or path). A signature/transaction observed on an excluded route must not
2276
- // populate currentAddress for later allowed-page events. backfill only ever
2277
- // *adds* an address (it no-ops when one is already known), so there is no
2278
- // stale state to clear here.
2279
- if (this.isTrackingSuppressed())
2280
- return;
2281
- if (this._evmAddress)
2282
- return;
2283
- this.setChainState('evm', { address: address, chainId: chainId });
2284
- };
2285
- /**
2286
- * Apply an EVM autocapture connect/switch while tracking is suppressed
2287
- * (opt-out / timezone / excluded host or path): never LEARN the wallet, but
2288
- * if it is a switch away from an already-learned EVM wallet, drop the stale
2289
- * one (which also clears the active-wallet cookie) so it can't attach to a
2290
- * later allowed-page event.
2291
- */
2292
- FormoAnalytics.prototype.clearStaleEvmWalletOnSwitchWhileSuppressed = function (address) {
2293
- var evmAddress = this._chainState.evm.address;
2294
- var incoming = (0, address_1.validateAndChecksumAddress)(address);
2295
- if (evmAddress && incoming && incoming !== evmAddress) {
2296
- this.clearChainState('evm');
2297
- }
2298
- };
2299
- /**
2300
- * Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
2301
- */
2302
- FormoAnalytics.prototype.pollTransactionReceipt = function (provider_2, transactionHash_2, payload_1) {
2303
- return __awaiter(this, arguments, void 0, function (provider, transactionHash, payload, maxAttempts, intervalMs) {
2304
- var attempts, poll;
2305
- var _this = this;
2306
- if (maxAttempts === void 0) { maxAttempts = 10; }
2307
- if (intervalMs === void 0) { intervalMs = 3000; }
2308
- return __generator(this, function (_a) {
2309
- attempts = 0;
2310
- if (!provider)
2311
- return [2 /*return*/];
2312
- poll = function () { return __awaiter(_this, void 0, void 0, function () {
2313
- var receipt, e_9;
2314
- return __generator(this, function (_a) {
2315
- switch (_a.label) {
2316
- case 0:
2317
- _a.trys.push([0, 2, , 3]);
2318
- return [4 /*yield*/, provider.request({
2319
- method: "eth_getTransactionReceipt",
2320
- params: [transactionHash],
2321
- })];
2322
- case 1:
2323
- receipt = (_a.sent());
2324
- if (receipt) {
2325
- // status: 1 = success, 0 = reverted
2326
- if (receipt.status === "0x1" || receipt.status === 1) {
2327
- this.transaction(__assign(__assign({ status: types_1.TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }));
2328
- return [2 /*return*/];
2329
- }
2330
- else if (receipt.status === "0x0" || receipt.status === 0) {
2331
- this.transaction(__assign(__assign({ status: types_1.TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }));
2332
- return [2 /*return*/];
2333
- }
2334
- }
2335
- return [3 /*break*/, 3];
2336
- case 2:
2337
- e_9 = _a.sent();
2338
- logger_1.logger.error("Error polling transaction receipt", e_9);
2339
- return [3 /*break*/, 3];
2340
- case 3:
2341
- attempts++;
2342
- if (attempts < maxAttempts) {
2343
- setTimeout(poll, intervalMs);
2344
- }
2345
- return [2 /*return*/];
2346
- }
2347
- });
2348
- }); };
2349
- poll();
2350
- return [2 /*return*/];
2351
- });
2352
- });
2353
- };
2354
- FormoAnalytics.prototype.removeProviderListeners = function (provider) {
2355
- var listeners = this._providerListenersMap.get(provider);
2356
- if (!listeners)
2357
- return;
2358
- for (var _i = 0, _a = Object.entries(listeners); _i < _a.length; _i++) {
2359
- var _b = _a[_i], event_2 = _b[0], fn = _b[1];
2360
- try {
2361
- provider.removeListener(event_2, fn);
2362
- }
2363
- catch (e) {
2364
- logger_1.logger.warn("Failed to remove listener for ".concat(String(event_2)), e);
2365
- }
2366
- }
2367
- this._providerListenersMap.delete(provider);
2368
- };
2369
1276
  // Explicitly untrack a provider: remove listeners, clear wrapper flag
2370
1277
  // and tracking
2371
- FormoAnalytics.prototype.untrackProvider = function (provider) {
2372
- try {
2373
- this.removeProviderListeners(provider);
2374
- this._trackedProviders.delete(provider);
2375
- if (this._provider === provider) {
2376
- this.clearActiveProvider();
2377
- }
2378
- }
2379
- catch (e) {
2380
- logger_1.logger.warn("Failed to untrack provider", e);
2381
- }
2382
- };
2383
1278
  // Debug/monitoring helpers
2384
1279
  FormoAnalytics.prototype.getTrackedProvidersCount = function () {
2385
- return this._trackedProviders.size;
1280
+ return this.evm.counts.trackedProviders;
2386
1281
  };
2387
1282
  /**
2388
1283
  * Get current provider state for debugging
2389
1284
  * @returns Object containing current provider state information
2390
1285
  */
2391
1286
  FormoAnalytics.prototype.getProviderState = function () {
2392
- return {
2393
- totalProviders: this._providers.length,
2394
- trackedProviders: this._trackedProviders.size,
2395
- seenProviders: this._seenProviders.size,
2396
- activeProvider: !!this._provider,
2397
- };
2398
- };
2399
- /**
2400
- * Clean up providers that are no longer available
2401
- * This helps maintain consistent state and prevents memory leaks
2402
- */
2403
- FormoAnalytics.prototype.cleanupUnavailableProviders = function () {
2404
- // Remove providers that are no longer in the current providers list
2405
- var currentProviderInstances = new Set(this._providers.map(function (detail) { return detail.provider; }));
2406
- for (var _i = 0, _a = Array.from(this._trackedProviders); _i < _a.length; _i++) {
2407
- var provider = _a[_i];
2408
- if (!currentProviderInstances.has(provider)) {
2409
- logger_1.logger.info("Cleaning up unavailable provider: ".concat(provider.constructor.name));
2410
- this.untrackProvider(provider);
2411
- }
2412
- }
2413
- };
2414
- /**
2415
- * Helper method to check if a provider is already wrapped
2416
- * @param provider The provider to check
2417
- * @param currentRequest The current request function
2418
- * @returns true if the provider is already wrapped
2419
- */
2420
- FormoAnalytics.prototype.isProviderAlreadyWrapped = function (provider, currentRequest) {
2421
- return !!(currentRequest &&
2422
- typeof currentRequest === "function" &&
2423
- currentRequest[types_1.WRAPPED_REQUEST_SYMBOL] &&
2424
- provider[types_1.WRAPPED_REQUEST_REF_SYMBOL] ===
2425
- currentRequest);
2426
- };
2427
- /**
2428
- * Handle provider mismatch by switching to the new provider and invalidating old tokens
2429
- * @param provider The new provider to switch to
2430
- */
2431
- FormoAnalytics.prototype.handleProviderMismatch = function (provider) {
2432
- // If this is a different provider, allow the switch
2433
- if (this._provider) {
2434
- // Clear any provider-specific state when switching
2435
- this.setChainState('evm', { address: undefined, chainId: undefined, provider: provider });
2436
- }
2437
- else {
2438
- this._provider = provider;
2439
- }
2440
- };
2441
- /**
2442
- * Determine which namespace a chainId belongs to.
2443
- */
2444
- FormoAnalytics.prototype.getNamespace = function (chainId) {
2445
- return (0, solana_1.isSolanaChainId)(chainId) ? 'solana' : 'evm';
2446
- };
2447
- /**
2448
- * Update per-chain state and sync the derived currentAddress/currentChainId.
2449
- * Accepts either a namespace string ('evm'/'solana') or a chainId number
2450
- * to resolve the namespace automatically. When a chainId number is passed,
2451
- * it is also stored as the namespace's chainId (unless explicitly overridden
2452
- * in the update object).
2453
- */
2454
- FormoAnalytics.prototype.setChainState = function (namespaceOrChainId, update) {
2455
- var namespace = typeof namespaceOrChainId === 'string'
2456
- ? namespaceOrChainId
2457
- : this.getNamespace(namespaceOrChainId);
2458
- var ns = this._chainState[namespace];
2459
- if ('address' in update)
2460
- ns.address = update.address;
2461
- if ('chainId' in update) {
2462
- ns.chainId = update.chainId;
2463
- }
2464
- else if (typeof namespaceOrChainId === 'number') {
2465
- ns.chainId = namespaceOrChainId;
2466
- }
2467
- if (namespace === 'evm' && 'provider' in update) {
2468
- ns.provider = update.provider;
2469
- }
2470
- this._activeNamespace = namespace;
2471
- this.syncDerivedState();
2472
- };
2473
- /**
2474
- * Clear per-chain state for a given namespace (or chainId) and sync derived state.
2475
- */
2476
- FormoAnalytics.prototype.clearChainState = function (namespaceOrChainId) {
2477
- var namespace = typeof namespaceOrChainId === 'string'
2478
- ? namespaceOrChainId
2479
- : this.getNamespace(namespaceOrChainId);
2480
- if (namespace === 'evm') {
2481
- this._chainState.evm = {};
2482
- }
2483
- else {
2484
- this._chainState.solana = {};
2485
- }
2486
- this.syncDerivedState();
2487
- };
2488
- /**
2489
- * Sync validated wallet/chain state into the SDK's central state
2490
- * WITHOUT emitting an event.
2491
- *
2492
- * Integrations (e.g. the wagmi handler) must call this on every
2493
- * connect / chain-change / disconnect - even when the corresponding
2494
- * autocapture event is disabled. Otherwise `currentChainId` stays
2495
- * stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
2496
- * check (which keys off `currentChainId`, not the event payload) can
2497
- * be bypassed, letting wallet activity on an excluded chain still be
2498
- * collected.
2499
- *
2500
- * - valid `address` present → record per-chain + derived state
2501
- * - `address` absent → clear chain state (disconnect)
2502
- */
2503
- FormoAnalytics.prototype.syncWalletState = function (params) {
2504
- var chainId = params.chainId, address = params.address;
2505
- if (this.isTrackingSuppressed()) {
2506
- // While suppressed (opt-out / timezone / excluded host or path) we must
2507
- // never LEARN a new wallet - but we must still CLEAR stale identity.
2508
- // Otherwise a disconnect or wallet switch observed on a suppressed route
2509
- // would leave the previously-learned address in memory and in the
2510
- // active-wallet cookie, attaching it to later allowed-page events.
2511
- if (!address) {
2512
- // Disconnect: drop the affected namespace(s).
2513
- if (chainId !== undefined && chainId !== null) {
2514
- this.clearChainState(chainId);
2515
- }
2516
- else {
2517
- this.clearChainState("evm");
2518
- this.clearChainState("solana");
2519
- }
2520
- return;
2521
- }
2522
- // Address present: a switch away from the wallet already learned in this
2523
- // namespace invalidates it. Drop the stale one without learning the new
2524
- // address; a fresh connect (nothing learned yet) or a re-confirmation of
2525
- // the same address is a no-op.
2526
- if (chainId === null || chainId === undefined)
2527
- return;
2528
- var namespace = this.getNamespace(chainId);
2529
- var namespaceAddress = this._chainState[namespace].address;
2530
- var validIncoming = (0, address_1.validateAddress)(address, chainId);
2531
- if (namespaceAddress &&
2532
- validIncoming &&
2533
- validIncoming !== namespaceAddress) {
2534
- this.clearChainState(chainId);
2535
- }
2536
- return;
2537
- }
2538
- if (!address) {
2539
- if (chainId !== undefined && chainId !== null) {
2540
- this.clearChainState(chainId);
2541
- }
2542
- else {
2543
- this.clearChainState("evm");
2544
- this.clearChainState("solana");
2545
- }
2546
- return;
2547
- }
2548
- if (chainId === null || chainId === undefined)
2549
- return;
2550
- var validAddress = (0, address_1.validateAddress)(address, chainId);
2551
- if (!validAddress) {
2552
- logger_1.logger.warn("syncWalletState: invalid address (\"".concat(address, "\") for chain ").concat(chainId));
2553
- return;
2554
- }
2555
- this.setChainState(chainId, { address: validAddress });
2556
- };
2557
- /**
2558
- * Synchronize currentAddress/currentChainId from the active namespace.
2559
- * Last-connected-chain-wins: _activeNamespace takes precedence.
2560
- */
2561
- FormoAnalytics.prototype.syncDerivedState = function () {
2562
- var active = this._activeNamespace;
2563
- if (active) {
2564
- var state = this._chainState[active];
2565
- if (state.address || state.chainId) {
2566
- this.currentAddress = state.address;
2567
- this.currentChainId = state.chainId;
2568
- this.persistActiveWallet();
2569
- return;
2570
- }
2571
- }
2572
- // Fall through to the other namespace
2573
- var other = active === 'evm' ? 'solana' : 'evm';
2574
- var otherState = this._chainState[other];
2575
- if (otherState.address || otherState.chainId) {
2576
- this.currentAddress = otherState.address;
2577
- this.currentChainId = otherState.chainId;
2578
- this._activeNamespace = other;
2579
- this.persistActiveWallet();
2580
- return;
2581
- }
2582
- this.currentAddress = undefined;
2583
- this.currentChainId = undefined;
2584
- this.persistActiveWallet();
2585
- };
2586
- /**
2587
- * Persist (or clear) the current wallet snapshot in a cookie so that the
2588
- * SDK can repopulate `currentAddress`/`currentChainId` at init on the next
2589
- * page load - closing the gap between page-show and wagmi/EIP-1193
2590
- * reconnection during which track()/page() events would otherwise ship
2591
- * with an empty address.
2592
- */
2593
- FormoAnalytics.prototype.persistActiveWallet = function () {
2594
- try {
2595
- // Visitor-level suppression (opt-out or excluded timezone): purge any
2596
- // prior snapshot - these are stable for the session, so deletion is safe.
2597
- if (this.isPersistedIdentityPurgeRequired()) {
2598
- (0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
2599
- return;
2600
- }
2601
- if (this.currentAddress) {
2602
- // Current-page exclusion (host/path): do not write a new snapshot while
2603
- // on an excluded route, but leave any existing cookie intact. A cookie
2604
- // written on an allowed page must survive a transient visit to an
2605
- // excluded one (passive navigation does not call this method).
2606
- if (this.isHostExcluded() || this.isPathExcluded()) {
2607
- return;
2608
- }
2609
- var value = JSON.stringify(__assign({ address: this.currentAddress }, (this.currentChainId !== undefined && { chainId: this.currentChainId })));
2610
- var domain = (0, cookiePolicy_1.getIdentityCookieDomain)(this.crossSubdomainCookies);
2611
- (0, storage_1.cookie)().set(constants_1.ACTIVE_WALLET_KEY, value, __assign(__assign({ path: "/", expires: new Date(Date.now() + constants_1.ACTIVE_WALLET_TTL_MS).toUTCString() }, (0, cookiePolicy_1.getIdentityCookieSecurity)()), (domain ? { domain: domain } : {})));
2612
- }
2613
- else {
2614
- // No active wallet → clear the snapshot. This runs even on an excluded
2615
- // route, so a disconnect/switch observed while suppressed actively
2616
- // removes stale identity instead of leaving it for later allowed events.
2617
- (0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
2618
- }
2619
- }
2620
- catch (err) {
2621
- logger_1.logger.warn("Failed to persist current wallet snapshot", err);
2622
- }
2623
- };
2624
- /**
2625
- * Seed `currentAddress`/`currentChainId` from the persisted snapshot, if
2626
- * any. Called once during construction before the first page hit fires.
2627
- */
2628
- FormoAnalytics.prototype.loadActiveWallet = function () {
2629
- try {
2630
- // Visitor-level suppression (opt-out or excluded timezone): never restore
2631
- // identity into memory; drop the stale snapshot.
2632
- if (this.isPersistedIdentityPurgeRequired()) {
2633
- (0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
2634
- return;
2635
- }
2636
- // Current-page exclusion (host/path): don't restore into memory while on
2637
- // an excluded route, but keep the cookie so a later allowed-page load can
2638
- // restore it.
2639
- if (this.isHostExcluded() || this.isPathExcluded()) {
2640
- return;
2641
- }
2642
- var raw = (0, storage_1.cookie)().get(constants_1.ACTIVE_WALLET_KEY);
2643
- if (!raw)
2644
- return;
2645
- var parsed = JSON.parse(raw);
2646
- if (!(parsed === null || parsed === void 0 ? void 0 : parsed.address))
2647
- return;
2648
- var namespace = (0, solana_1.isSolanaChainId)(parsed.chainId) ? "solana" : "evm";
2649
- var validated = (0, address_1.validateAddress)(parsed.address, parsed.chainId);
2650
- if (!validated) {
2651
- (0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
2652
- return;
2653
- }
2654
- var ns = this._chainState[namespace];
2655
- ns.address = validated;
2656
- if (parsed.chainId !== undefined)
2657
- ns.chainId = parsed.chainId;
2658
- this._activeNamespace = namespace;
2659
- this.currentAddress = validated;
2660
- this.currentChainId = parsed.chainId;
2661
- }
2662
- catch (err) {
2663
- logger_1.logger.warn("Failed to restore persisted wallet snapshot", err);
2664
- (0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
2665
- }
2666
- };
2667
- /**
2668
- * Helper method to clear the active provider state
2669
- * Centralizes provider clearing logic for consistency
2670
- */
2671
- FormoAnalytics.prototype.clearActiveProvider = function () {
2672
- this._provider = undefined;
2673
- };
2674
- /**
2675
- * Helper method to safely add a provider detail to _providers array, ensuring no duplicates
2676
- * @param detail The provider detail to add
2677
- * @returns true if the provider was added, false if it was already present
2678
- */
2679
- FormoAnalytics.prototype.safeAddProviderDetail = function (detail) {
2680
- var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
2681
- if (!provider)
2682
- return false;
2683
- // Check if provider already exists in _providers array
2684
- var alreadyExists = this._providers.some(function (existing) { return existing.provider === provider; });
2685
- if (!alreadyExists) {
2686
- // Add to providers array and mark as seen
2687
- this._providers = __spreadArray(__spreadArray([], this._providers, true), [detail], false);
2688
- this._seenProviders.add(provider);
2689
- return true;
2690
- }
2691
- else {
2692
- // Ensure provider is marked as seen even if it already exists in _providers
2693
- this._seenProviders.add(provider);
2694
- return false;
2695
- }
1287
+ return __assign(__assign({}, this.evm.counts), { activeProvider: !!this._provider });
2696
1288
  };
2697
1289
  return FormoAnalytics;
2698
1290
  }());