@formo/analytics 1.19.6 → 1.19.7

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.
@@ -45,10 +45,19 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
45
45
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
46
  }
47
47
  };
48
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
49
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
50
+ if (ar || !(i in from)) {
51
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
52
+ ar[i] = from[i];
53
+ }
54
+ }
55
+ return to.concat(ar || Array.prototype.slice.call(from));
56
+ };
48
57
  import { createStore } from "mipd";
49
- import { EVENTS_API_URL, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_CURRENT_URL_KEY, SESSION_USER_ID_KEY, SESSION_WALLET_DETECTED_KEY, } from "./constants";
58
+ import { EVENTS_API_URL, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_CURRENT_URL_KEY, SESSION_USER_ID_KEY, SESSION_WALLET_DETECTED_KEY, DEFAULT_PROVIDER_ICON, } from "./constants";
50
59
  import { cookie, EventManager, EventQueue, initStorageManager, logger, Logger, } from "./lib";
51
- import { SignatureStatus, TransactionStatus, } from "./types";
60
+ import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, } from "./types";
52
61
  import { toChecksumAddress } from "./utils";
53
62
  import { getValidAddress } from "./utils/address";
54
63
  import { isLocalhost } from "./validators";
@@ -59,8 +68,23 @@ var FormoAnalytics = /** @class */ (function () {
59
68
  var _a, _b;
60
69
  this.writeKey = writeKey;
61
70
  this.options = options;
62
- this._providerListeners = {};
71
+ this._providerListenersMap = new Map();
72
+ /**
73
+ * EIP-6963 provider details discovered through the browser
74
+ * This array contains all available providers with their metadata
75
+ */
63
76
  this._providers = [];
77
+ /**
78
+ * Set of providers that have been tracked with event listeners
79
+ * This is separate from _providers because:
80
+ * - _providers contains all discovered providers (EIP-6963)
81
+ * - _trackedProviders contains only providers that have been set up with listeners
82
+ * - A provider can be discovered but not yet tracked (e.g., during initialization)
83
+ * - A provider can be tracked but later removed from discovery
84
+ */
85
+ this._trackedProviders = new Set();
86
+ // Set to efficiently track seen providers for deduplication and O(1) lookup
87
+ this._seenProviders = new Set();
64
88
  this.currentUserId = "";
65
89
  this.config = {
66
90
  writeKey: writeKey,
@@ -89,14 +113,43 @@ var FormoAnalytics = /** @class */ (function () {
89
113
  maxQueueSize: options.maxQueueSize,
90
114
  flushInterval: options.flushInterval,
91
115
  }));
92
- // TODO: replace with eip6963
93
- var provider = options.provider || (window === null || window === void 0 ? void 0 : window.ethereum);
116
+ // Handle initial provider (injected) as fallback; listeners for EIP-6963 are added later
117
+ var provider = undefined;
118
+ var optProvider = options.provider;
119
+ if (optProvider) {
120
+ provider = optProvider;
121
+ }
122
+ else if (typeof window !== 'undefined' && window.ethereum) {
123
+ provider = window.ethereum;
124
+ }
94
125
  if (provider) {
95
126
  this.trackProvider(provider);
96
127
  }
97
128
  this.trackPageHit();
98
129
  this.trackPageHits();
99
130
  }
131
+ /**
132
+ * Helper method to check if a provider is different from the currently active one
133
+ * @param provider The provider to check
134
+ * @returns true if there's a provider mismatch, false otherwise
135
+ */
136
+ FormoAnalytics.prototype.isProviderMismatch = function (provider) {
137
+ // Only consider it a mismatch if we have an active provider AND the provider is different
138
+ // This allows legitimate provider switching while preventing race conditions
139
+ return this._provider != null && this._provider !== provider;
140
+ };
141
+ /**
142
+ * Check if a provider is in a valid state for switching
143
+ * @param provider The provider to validate
144
+ * @returns true if the provider is in a valid state
145
+ */
146
+ FormoAnalytics.prototype.isProviderInValidState = function (provider) {
147
+ // Basic validation: ensure provider exists and has required methods
148
+ return (provider &&
149
+ typeof provider.request === 'function' &&
150
+ typeof provider.on === 'function' &&
151
+ typeof provider.removeListener === 'function');
152
+ };
100
153
  FormoAnalytics.init = function (writeKey, options) {
101
154
  return __awaiter(this, void 0, void 0, function () {
102
155
  var analytics, _a;
@@ -114,6 +167,7 @@ var FormoAnalytics = /** @class */ (function () {
114
167
  return [4 /*yield*/, analytics.detectWallets(analytics._providers)];
115
168
  case 2:
116
169
  _b.sent();
170
+ analytics.trackProviders(analytics._providers);
117
171
  return [2 /*return*/, analytics];
118
172
  }
119
173
  });
@@ -151,6 +205,7 @@ var FormoAnalytics = /** @class */ (function () {
151
205
  this.currentUserId = undefined;
152
206
  cookie().remove(LOCAL_ANONYMOUS_ID_KEY);
153
207
  cookie().remove(SESSION_USER_ID_KEY);
208
+ cookie().remove(SESSION_WALLET_DETECTED_KEY);
154
209
  };
155
210
  /**
156
211
  * Emits a connect wallet event.
@@ -159,25 +214,30 @@ var FormoAnalytics = /** @class */ (function () {
159
214
  * @param {IFormoEventProperties} properties
160
215
  * @param {IFormoEventContext} context
161
216
  * @param {(...args: unknown[]) => void} callback
162
- * @throws {Error} If chainId or address is empty
163
217
  * @returns {Promise<void>}
164
218
  */
165
219
  FormoAnalytics.prototype.connect = function (_a, properties_1, context_1, callback_1) {
166
220
  return __awaiter(this, arguments, void 0, function (_b, properties, context, callback) {
167
- var validAddress;
221
+ var checksummedAddress;
168
222
  var chainId = _b.chainId, address = _b.address;
169
223
  return __generator(this, function (_c) {
170
224
  switch (_c.label) {
171
225
  case 0:
172
- if (!chainId) {
173
- logger.warn("Connect: Chain ID cannot be empty");
226
+ if (chainId === null || chainId === undefined) {
227
+ logger.warn("Connect: Chain ID cannot be null or undefined");
228
+ return [2 /*return*/];
174
229
  }
175
230
  if (!address) {
176
231
  logger.warn("Connect: Address cannot be empty");
232
+ return [2 /*return*/];
177
233
  }
178
234
  this.currentChainId = chainId;
179
- validAddress = getValidAddress(address);
180
- this.currentAddress = validAddress ? toChecksumAddress(validAddress) : undefined;
235
+ checksummedAddress = this.validateAndChecksumAddress(address);
236
+ if (!checksummedAddress) {
237
+ logger.warn("Connect: Invalid address provided (\"".concat(address, "\"). Please provide a valid Ethereum address in checksum format."));
238
+ return [2 /*return*/];
239
+ }
240
+ this.currentAddress = checksummedAddress;
181
241
  return [4 /*yield*/, this.trackEvent(EventType.CONNECT, {
182
242
  chainId: chainId,
183
243
  address: this.currentAddress,
@@ -200,16 +260,24 @@ var FormoAnalytics = /** @class */ (function () {
200
260
  */
201
261
  FormoAnalytics.prototype.disconnect = function (params, properties, context, callback) {
202
262
  return __awaiter(this, void 0, void 0, function () {
203
- var chainId, address;
263
+ var chainId, address, providerInfo, disconnectProperties;
204
264
  return __generator(this, function (_a) {
205
265
  switch (_a.label) {
206
266
  case 0:
207
267
  chainId = (params === null || params === void 0 ? void 0 : params.chainId) || this.currentChainId;
208
268
  address = (params === null || params === void 0 ? void 0 : params.address) || this.currentAddress;
209
- return [4 /*yield*/, this.trackEvent(EventType.DISCONNECT, {
210
- chainId: chainId,
211
- address: address,
212
- }, properties, context, callback)];
269
+ providerInfo = this._provider ? this.getProviderInfo(this._provider) : null;
270
+ logger.info("Disconnect: Emitting disconnect event with:", {
271
+ chainId: chainId,
272
+ address: address,
273
+ providerName: providerInfo === null || providerInfo === void 0 ? void 0 : providerInfo.name,
274
+ rdns: providerInfo === null || providerInfo === void 0 ? void 0 : providerInfo.rdns
275
+ });
276
+ disconnectProperties = __assign(__assign({}, (providerInfo && {
277
+ providerName: providerInfo.name,
278
+ rdns: providerInfo.rdns
279
+ })), properties);
280
+ return [4 /*yield*/, this.trackEvent(EventType.DISCONNECT, __assign(__assign({}, (chainId && { chainId: chainId })), (address && { address: address })), disconnectProperties, context, callback)];
213
281
  case 1:
214
282
  _a.sent();
215
283
  this.currentAddress = undefined;
@@ -227,8 +295,6 @@ var FormoAnalytics = /** @class */ (function () {
227
295
  * @param {IFormoEventProperties} properties
228
296
  * @param {IFormoEventContext} context
229
297
  * @param {(...args: unknown[]) => void} callback
230
- * @throws {Error} If chainId is empty, zero, or not a valid number
231
- * @throws {Error} If no address is provided and no previous address is recorded
232
298
  * @returns {Promise<void>}
233
299
  */
234
300
  FormoAnalytics.prototype.chain = function (_a, properties_1, context_1, callback_1) {
@@ -238,13 +304,16 @@ var FormoAnalytics = /** @class */ (function () {
238
304
  switch (_c.label) {
239
305
  case 0:
240
306
  if (!chainId || Number(chainId) === 0) {
241
- throw new Error("FormoAnalytics::chain: chainId cannot be empty or 0");
307
+ logger.warn("FormoAnalytics::chain: chainId cannot be empty or 0");
308
+ return [2 /*return*/];
242
309
  }
243
310
  if (isNaN(Number(chainId))) {
244
- throw new Error("FormoAnalytics::chain: chainId must be a valid decimal number");
311
+ logger.warn("FormoAnalytics::chain: chainId must be a valid decimal number");
312
+ return [2 /*return*/];
245
313
  }
246
314
  if (!address && !this.currentAddress) {
247
- throw new Error("FormoAnalytics::chain: address was empty and no previous address has been recorded");
315
+ logger.warn("FormoAnalytics::chain: address was empty and no previous address has been recorded");
316
+ return [2 /*return*/];
248
317
  }
249
318
  this.currentChainId = chainId;
250
319
  return [4 /*yield*/, this.trackEvent(EventType.CHAIN, {
@@ -324,27 +393,28 @@ var FormoAnalytics = /** @class */ (function () {
324
393
  FormoAnalytics.prototype.identify = function (params, properties, context, callback) {
325
394
  return __awaiter(this, void 0, void 0, function () {
326
395
  var _i, _a, providerDetail, provider, address_1, err_1, userId, address, providerName, rdns, validAddress, e_1;
327
- return __generator(this, function (_b) {
328
- switch (_b.label) {
396
+ var _b;
397
+ return __generator(this, function (_c) {
398
+ switch (_c.label) {
329
399
  case 0:
330
- _b.trys.push([0, 11, , 12]);
400
+ _c.trys.push([0, 11, , 12]);
331
401
  if (!!params) return [3 /*break*/, 9];
332
402
  // If no params provided, auto-identify
333
403
  logger.info("Auto-identifying with providers:", this._providers.map(function (p) { return p.info.name; }));
334
404
  _i = 0, _a = this._providers;
335
- _b.label = 1;
405
+ _c.label = 1;
336
406
  case 1:
337
407
  if (!(_i < _a.length)) return [3 /*break*/, 8];
338
408
  providerDetail = _a[_i];
339
409
  provider = providerDetail.provider;
340
410
  if (!provider)
341
411
  return [3 /*break*/, 7];
342
- _b.label = 2;
412
+ _c.label = 2;
343
413
  case 2:
344
- _b.trys.push([2, 6, , 7]);
414
+ _c.trys.push([2, 6, , 7]);
345
415
  return [4 /*yield*/, this.getAddress(provider)];
346
416
  case 3:
347
- address_1 = _b.sent();
417
+ address_1 = _c.sent();
348
418
  if (!address_1) return [3 /*break*/, 5];
349
419
  logger.info("Auto-identifying", address_1, providerDetail.info.name, providerDetail.info.rdns);
350
420
  // NOTE: do not set this.currentAddress without explicit connect or identify
@@ -355,11 +425,11 @@ var FormoAnalytics = /** @class */ (function () {
355
425
  }, properties, context, callback)];
356
426
  case 4:
357
427
  // NOTE: do not set this.currentAddress without explicit connect or identify
358
- _b.sent();
359
- _b.label = 5;
428
+ _c.sent();
429
+ _c.label = 5;
360
430
  case 5: return [3 /*break*/, 7];
361
431
  case 6:
362
- err_1 = _b.sent();
432
+ err_1 = _c.sent();
363
433
  logger.error("Failed to identify provider ".concat(providerDetail.info.name, ":"), err_1);
364
434
  return [3 /*break*/, 7];
365
435
  case 7:
@@ -369,24 +439,32 @@ var FormoAnalytics = /** @class */ (function () {
369
439
  case 9:
370
440
  userId = params.userId, address = params.address, providerName = params.providerName, rdns = params.rdns;
371
441
  logger.info("Identify", address, userId, providerName, rdns);
372
- validAddress = getValidAddress(address);
373
- if (validAddress)
374
- this.currentAddress = toChecksumAddress(validAddress);
442
+ validAddress = undefined;
443
+ if (address) {
444
+ validAddress = this.validateAndChecksumAddress(address);
445
+ this.currentAddress = validAddress || undefined;
446
+ if (!validAddress) {
447
+ (_b = logger.warn) === null || _b === void 0 ? void 0 : _b.call(logger, "Invalid address provided to identify:", address);
448
+ }
449
+ }
450
+ else {
451
+ this.currentAddress = undefined;
452
+ }
375
453
  if (userId) {
376
454
  this.currentUserId = userId;
377
455
  cookie().set(SESSION_USER_ID_KEY, userId);
378
456
  }
379
457
  return [4 /*yield*/, this.trackEvent(EventType.IDENTIFY, {
380
- address: validAddress ? toChecksumAddress(validAddress) : undefined,
458
+ address: validAddress,
381
459
  providerName: providerName,
382
460
  userId: userId,
383
461
  rdns: rdns,
384
462
  }, properties, context, callback)];
385
463
  case 10:
386
- _b.sent();
464
+ _c.sent();
387
465
  return [3 /*break*/, 12];
388
466
  case 11:
389
- e_1 = _b.sent();
467
+ e_1 = _c.sent();
390
468
  logger.log("identify error", e_1);
391
469
  return [3 /*break*/, 12];
392
470
  case 12: return [2 /*return*/];
@@ -449,138 +527,226 @@ var FormoAnalytics = /** @class */ (function () {
449
527
  FormoAnalytics.prototype.trackProvider = function (provider) {
450
528
  logger.info("trackProvider", provider);
451
529
  try {
452
- if (provider === this._provider) {
530
+ if (!provider)
531
+ return;
532
+ if (this._trackedProviders.has(provider)) {
453
533
  logger.warn("TrackProvider: Provider already tracked.");
454
534
  return;
455
535
  }
456
- this.currentChainId = undefined;
457
- this.currentAddress = undefined;
458
- if (this._provider) {
459
- var actions = Object.keys(this._providerListeners);
460
- for (var _i = 0, actions_1 = actions; _i < actions_1.length; _i++) {
461
- var action = actions_1[_i];
462
- this._provider.removeListener(action, this._providerListeners[action]);
463
- delete this._providerListeners[action];
536
+ // Register listeners for this provider first
537
+ this.registerAccountsChangedListener(provider);
538
+ this.registerChainChangedListener(provider);
539
+ this.registerConnectListener(provider);
540
+ this.registerRequestListeners(provider);
541
+ this.registerDisconnectListener(provider);
542
+ // Only add to tracked providers after all listeners are successfully registered
543
+ this._trackedProviders.add(provider);
544
+ }
545
+ catch (error) {
546
+ logger.error("Error tracking provider:", error);
547
+ }
548
+ };
549
+ FormoAnalytics.prototype.trackProviders = function (providers) {
550
+ try {
551
+ for (var _i = 0, providers_1 = providers; _i < providers_1.length; _i++) {
552
+ var eip6963ProviderDetail = providers_1[_i];
553
+ var provider = eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.provider;
554
+ if (provider && !this._trackedProviders.has(provider)) {
555
+ this.trackProvider(provider);
464
556
  }
465
557
  }
466
- this._provider = provider;
467
- // Register listeners for web3 provider events
468
- this.registerAccountsChangedListener();
469
- this.registerChainChangedListener();
470
- this.registerConnectListener();
471
- this.registerRequestListeners();
472
558
  }
473
559
  catch (error) {
474
- logger.error("Error tracking provider:", error);
560
+ logger.error("Failed to track EIP-6963 providers during initialization:", error);
475
561
  }
476
562
  };
477
- FormoAnalytics.prototype.registerAccountsChangedListener = function () {
563
+ FormoAnalytics.prototype.addProviderListener = function (provider, event, listener) {
564
+ var map = this._providerListenersMap.get(provider) || {};
565
+ map[event] = listener;
566
+ this._providerListenersMap.set(provider, map);
567
+ };
568
+ FormoAnalytics.prototype.registerAccountsChangedListener = function (provider) {
478
569
  var _this = this;
479
- var _a;
480
570
  logger.info("registerAccountsChangedListener");
481
571
  var listener = function () {
482
572
  var args = [];
483
573
  for (var _i = 0; _i < arguments.length; _i++) {
484
574
  args[_i] = arguments[_i];
485
575
  }
486
- return _this.onAccountsChanged(args[0]);
576
+ return _this.onAccountsChanged(provider, args[0]);
487
577
  };
488
- (_a = this._provider) === null || _a === void 0 ? void 0 : _a.on("accountsChanged", listener);
489
- this._providerListeners["accountsChanged"] = listener;
578
+ provider.on("accountsChanged", listener);
579
+ this.addProviderListener(provider, "accountsChanged", listener);
490
580
  };
491
- FormoAnalytics.prototype.onAccountsChanged = function (accounts) {
581
+ FormoAnalytics.prototype.onAccountsChanged = function (provider, accounts) {
492
582
  return __awaiter(this, void 0, void 0, function () {
493
- var validAddress, address, _a;
494
- return __generator(this, function (_b) {
495
- switch (_b.label) {
583
+ var error_1, address, nextChainId, wasDisconnected, isProviderSwitch, hadPreviousConnection, error_2, providerInfo, effectiveChainId;
584
+ var _a, _b, _c;
585
+ return __generator(this, function (_d) {
586
+ switch (_d.label) {
496
587
  case 0:
497
588
  logger.info("onAccountsChanged", accounts);
498
- if (!(accounts.length === 0)) return [3 /*break*/, 2];
499
- // Handle wallet disconnect
500
- return [4 /*yield*/, this.disconnect()];
589
+ if (!(accounts.length === 0)) return [3 /*break*/, 7];
590
+ if (!(this._provider === provider)) return [3 /*break*/, 5];
591
+ logger.info("OnAccountsChanged: Detecting disconnect, current state:", {
592
+ currentAddress: this.currentAddress,
593
+ currentChainId: this.currentChainId,
594
+ providerMatch: this._provider === provider
595
+ });
596
+ _d.label = 1;
501
597
  case 1:
502
- // Handle wallet disconnect
503
- _b.sent();
504
- return [2 /*return*/];
598
+ _d.trys.push([1, 3, , 4]);
599
+ // Pass current state explicitly to ensure we have the data for the disconnect event
600
+ return [4 /*yield*/, this.disconnect({
601
+ chainId: this.currentChainId,
602
+ address: this.currentAddress
603
+ })];
505
604
  case 2:
506
- validAddress = getValidAddress(accounts[0]);
507
- if (!validAddress) {
605
+ // Pass current state explicitly to ensure we have the data for the disconnect event
606
+ _d.sent();
607
+ return [3 /*break*/, 4];
608
+ case 3:
609
+ error_1 = _d.sent();
610
+ logger.error("Failed to disconnect provider on accountsChanged", error_1);
611
+ return [3 /*break*/, 4];
612
+ case 4: return [3 /*break*/, 6];
613
+ case 5:
614
+ logger.info("OnAccountsChanged: Ignoring disconnect for non-active provider");
615
+ _d.label = 6;
616
+ case 6: return [2 /*return*/];
617
+ case 7:
618
+ address = this.validateAndChecksumAddress(accounts[0]);
619
+ if (!address) {
508
620
  logger.warn("onAccountsChanged: Invalid address received", accounts[0]);
509
621
  return [2 /*return*/];
510
622
  }
511
- address = toChecksumAddress(validAddress);
512
- if (address === this.currentAddress) {
513
- // We have already reported this address
623
+ // If both the provider and address are the same, no-op. Allow provider switches even if address is the same.
624
+ if (this._provider === provider && address === this.currentAddress) {
514
625
  return [2 /*return*/];
515
626
  }
516
- // Handle wallet connect
627
+ return [4 /*yield*/, this.getCurrentChainId(provider)];
628
+ case 8:
629
+ nextChainId = _d.sent();
630
+ wasDisconnected = !this.currentAddress;
631
+ isProviderSwitch = this._provider && this._provider !== provider;
632
+ logger.info("OnAccountsChanged: Provider switching analysis:", {
633
+ currentProvider: ((_b = (_a = this._provider) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name) || 'none',
634
+ newProvider: ((_c = provider === null || provider === void 0 ? void 0 : provider.constructor) === null || _c === void 0 ? void 0 : _c.name) || 'unknown',
635
+ currentAddress: this.currentAddress,
636
+ wasDisconnected: wasDisconnected,
637
+ isProviderMismatch: isProviderSwitch
638
+ });
639
+ if (!isProviderSwitch) return [3 /*break*/, 13];
640
+ hadPreviousConnection = !!this.currentAddress;
641
+ if (!hadPreviousConnection) return [3 /*break*/, 12];
642
+ logger.info("OnAccountsChanged: Detected provider switch with existing connection - emitting disconnect for previous provider");
643
+ _d.label = 9;
644
+ case 9:
645
+ _d.trys.push([9, 11, , 12]);
646
+ return [4 /*yield*/, this.disconnect({
647
+ chainId: this.currentChainId,
648
+ address: this.currentAddress
649
+ })];
650
+ case 10:
651
+ _d.sent();
652
+ return [3 /*break*/, 12];
653
+ case 11:
654
+ error_2 = _d.sent();
655
+ logger.error("Failed to emit disconnect during provider switch:", error_2);
656
+ return [3 /*break*/, 12];
657
+ case 12:
658
+ // Validate that the new provider is in a valid state before switching
659
+ if (this.isProviderInValidState(provider)) {
660
+ logger.info("OnAccountsChanged: Handling provider mismatch - switching providers");
661
+ this.handleProviderMismatch(provider);
662
+ }
663
+ else {
664
+ logger.warn("Provider switching blocked: new provider is not in a valid state");
665
+ return [2 /*return*/];
666
+ }
667
+ _d.label = 13;
668
+ case 13:
669
+ // Commit new active provider and state
670
+ this._provider = provider;
517
671
  this.currentAddress = address;
518
- _a = this;
519
- return [4 /*yield*/, this.getCurrentChainId()];
520
- case 3:
521
- _a.currentChainId = _b.sent();
522
- this.connect({ chainId: this.currentChainId, address: address });
672
+ this.currentChainId = nextChainId;
673
+ providerInfo = this.getProviderInfo(provider);
674
+ logger.info("OnAccountsChanged: Detected wallet connection, emitting connect event", {
675
+ chainId: nextChainId,
676
+ address: address,
677
+ wasDisconnected: wasDisconnected,
678
+ isProviderSwitch: isProviderSwitch,
679
+ providerName: providerInfo.name,
680
+ rdns: providerInfo.rdns,
681
+ hasChainId: !!nextChainId
682
+ });
683
+ effectiveChainId = nextChainId || 0;
684
+ if (effectiveChainId === 0) {
685
+ logger.info("OnAccountsChanged: Using fallback chainId 0 for connect event");
686
+ }
687
+ this.connect({
688
+ chainId: effectiveChainId,
689
+ address: address
690
+ }, {
691
+ providerName: providerInfo.name,
692
+ rdns: providerInfo.rdns
693
+ }).catch(function (error) {
694
+ logger.error("Failed to track connect event during account change:", error);
695
+ });
523
696
  return [2 /*return*/];
524
697
  }
525
698
  });
526
699
  });
527
700
  };
528
- FormoAnalytics.prototype.registerChainChangedListener = function () {
701
+ FormoAnalytics.prototype.registerChainChangedListener = function (provider) {
529
702
  var _this = this;
530
- var _a;
531
703
  logger.info("registerChainChangedListener");
532
704
  var listener = function () {
533
705
  var args = [];
534
706
  for (var _i = 0; _i < arguments.length; _i++) {
535
707
  args[_i] = arguments[_i];
536
708
  }
537
- return _this.onChainChanged(args[0]);
709
+ return _this.onChainChanged(provider, args[0]);
538
710
  };
539
- (_a = this.provider) === null || _a === void 0 ? void 0 : _a.on("chainChanged", listener);
540
- this._providerListeners["chainChanged"] = listener;
711
+ provider.on("chainChanged", listener);
712
+ this.addProviderListener(provider, "chainChanged", listener);
541
713
  };
542
- FormoAnalytics.prototype.onChainChanged = function (chainIdHex) {
714
+ FormoAnalytics.prototype.onChainChanged = function (provider, chainIdHex) {
543
715
  return __awaiter(this, void 0, void 0, function () {
544
- var address, validAddress;
716
+ var nextChainId;
545
717
  return __generator(this, function (_a) {
546
- switch (_a.label) {
547
- case 0:
548
- logger.info("onChainChanged", chainIdHex);
549
- this.currentChainId = parseChainId(chainIdHex);
550
- if (!!this.currentAddress) return [3 /*break*/, 2];
551
- if (!this.provider) {
552
- logger.info("OnChainChanged: Provider not found. CHAIN_CHANGED not reported");
553
- return [2 /*return*/, Promise.resolve()];
554
- }
555
- return [4 /*yield*/, this.getAddress()];
556
- case 1:
557
- address = _a.sent();
558
- if (!address) {
559
- logger.info("OnChainChanged: Unable to fetch or store connected address");
560
- return [2 /*return*/, Promise.resolve()];
561
- }
562
- validAddress = getValidAddress(address);
563
- this.currentAddress = validAddress ? toChecksumAddress(validAddress) : undefined;
564
- _a.label = 2;
565
- case 2:
566
- // Proceed only if the address exists
567
- if (this.currentAddress) {
568
- return [2 /*return*/, this.chain({
569
- chainId: this.currentChainId,
570
- address: this.currentAddress,
571
- })];
572
- }
573
- else {
574
- logger.info("OnChainChanged: Current connected address is null despite fetch attempt");
575
- }
576
- return [2 /*return*/];
718
+ logger.info("onChainChanged", chainIdHex);
719
+ nextChainId = parseChainId(chainIdHex);
720
+ // Only handle chain changes for the active provider (or if none is set yet)
721
+ if (this.isProviderMismatch(provider)) {
722
+ this.handleProviderMismatch(provider);
723
+ }
724
+ // Chain changes only matter for connected users
725
+ if (!this.currentAddress) {
726
+ logger.info("OnChainChanged: No current address, user appears disconnected");
727
+ return [2 /*return*/, Promise.resolve()];
728
+ }
729
+ // Set provider if none exists
730
+ if (!this._provider) {
731
+ this._provider = provider;
577
732
  }
733
+ this.currentChainId = nextChainId;
734
+ try {
735
+ // This is just a chain change since we already confirmed currentAddress exists
736
+ return [2 /*return*/, this.chain({
737
+ chainId: this.currentChainId,
738
+ address: this.currentAddress,
739
+ })];
740
+ }
741
+ catch (error) {
742
+ logger.error("OnChainChanged: Failed to emit chain event:", error);
743
+ }
744
+ return [2 /*return*/];
578
745
  });
579
746
  });
580
747
  };
581
- FormoAnalytics.prototype.registerConnectListener = function () {
748
+ FormoAnalytics.prototype.registerConnectListener = function (provider) {
582
749
  var _this = this;
583
- var _a;
584
750
  logger.info("registerConnectListener");
585
751
  var listener = function () {
586
752
  var args = [];
@@ -588,14 +754,51 @@ var FormoAnalytics = /** @class */ (function () {
588
754
  args[_i] = arguments[_i];
589
755
  }
590
756
  var connection = args[0];
591
- _this.onConnected(connection);
757
+ _this.onConnected(provider, connection);
592
758
  };
593
- (_a = this._provider) === null || _a === void 0 ? void 0 : _a.on("connect", listener);
594
- this._providerListeners["connect"] = listener;
759
+ provider.on("connect", listener);
760
+ this.addProviderListener(provider, "connect", listener);
761
+ };
762
+ FormoAnalytics.prototype.registerDisconnectListener = function (provider) {
763
+ var _this = this;
764
+ logger.info("registerDisconnectListener");
765
+ var listener = function (_error) { return __awaiter(_this, void 0, void 0, function () {
766
+ var e_2;
767
+ return __generator(this, function (_a) {
768
+ switch (_a.label) {
769
+ case 0:
770
+ if (this._provider !== provider)
771
+ return [2 /*return*/];
772
+ logger.info("OnDisconnect: Wallet disconnect event received, current state:", {
773
+ currentAddress: this.currentAddress,
774
+ currentChainId: this.currentChainId
775
+ });
776
+ _a.label = 1;
777
+ case 1:
778
+ _a.trys.push([1, 3, , 4]);
779
+ // Pass current state explicitly to ensure we have the data for the disconnect event
780
+ return [4 /*yield*/, this.disconnect({
781
+ chainId: this.currentChainId,
782
+ address: this.currentAddress
783
+ })];
784
+ case 2:
785
+ // Pass current state explicitly to ensure we have the data for the disconnect event
786
+ _a.sent();
787
+ return [3 /*break*/, 4];
788
+ case 3:
789
+ e_2 = _a.sent();
790
+ logger.error("Error during disconnect in disconnect listener", e_2);
791
+ return [3 /*break*/, 4];
792
+ case 4: return [2 /*return*/];
793
+ }
794
+ });
795
+ }); };
796
+ provider.on("disconnect", listener);
797
+ this.addProviderListener(provider, "disconnect", listener);
595
798
  };
596
- FormoAnalytics.prototype.onConnected = function (connection) {
799
+ FormoAnalytics.prototype.onConnected = function (provider, connection) {
597
800
  return __awaiter(this, void 0, void 0, function () {
598
- var chainId, address, e_2;
801
+ var chainId, address, wasDisconnected, providerInfo, effectiveChainId, e_3;
599
802
  return __generator(this, function (_a) {
600
803
  switch (_a.label) {
601
804
  case 0:
@@ -603,53 +806,91 @@ var FormoAnalytics = /** @class */ (function () {
603
806
  _a.label = 1;
604
807
  case 1:
605
808
  _a.trys.push([1, 3, , 4]);
606
- if (!connection || typeof connection.chainId !== 'string')
809
+ if (!(connection === null || connection === void 0 ? void 0 : connection.chainId) || typeof connection.chainId !== 'string')
607
810
  return [2 /*return*/];
608
811
  chainId = parseChainId(connection.chainId);
609
- return [4 /*yield*/, this.getAddress()];
812
+ return [4 /*yield*/, this.getAddress(provider)];
610
813
  case 2:
611
814
  address = _a.sent();
612
- if (chainId !== null && chainId !== undefined && address) {
613
- this.connect({ chainId: chainId, address: address });
815
+ if (chainId && address) {
816
+ wasDisconnected = !this.currentAddress;
817
+ // Set provider if none exists
818
+ if (!this._provider) {
819
+ this._provider = provider;
820
+ }
821
+ this.currentChainId = chainId;
822
+ this.currentAddress = this.validateAndChecksumAddress(address) || undefined;
823
+ // Always emit connect event for better analytics capture
824
+ if (this.currentAddress) {
825
+ providerInfo = this.getProviderInfo(provider);
826
+ logger.info("OnConnected: Detected wallet connection, emitting connect event", {
827
+ chainId: chainId,
828
+ wasDisconnected: wasDisconnected,
829
+ providerName: providerInfo.name,
830
+ rdns: providerInfo.rdns,
831
+ hasChainId: !!chainId
832
+ });
833
+ effectiveChainId = chainId || 0;
834
+ if (effectiveChainId === 0) {
835
+ logger.info("OnConnected: Using fallback chainId 0 for connect event");
836
+ }
837
+ this.connect({
838
+ chainId: effectiveChainId,
839
+ address: address
840
+ }, {
841
+ providerName: providerInfo.name,
842
+ rdns: providerInfo.rdns
843
+ }).catch(function (error) {
844
+ logger.error("Failed to track connect event during provider connection:", error);
845
+ });
846
+ }
614
847
  }
615
848
  return [3 /*break*/, 4];
616
849
  case 3:
617
- e_2 = _a.sent();
618
- logger.error("Error handling connect event", e_2);
850
+ e_3 = _a.sent();
851
+ logger.error("Error handling connect event", e_3);
619
852
  return [3 /*break*/, 4];
620
853
  case 4: return [2 /*return*/];
621
854
  }
622
855
  });
623
856
  });
624
857
  };
625
- FormoAnalytics.prototype.registerRequestListeners = function () {
858
+ FormoAnalytics.prototype.registerRequestListeners = function (provider) {
626
859
  var _this = this;
627
- var _a;
628
860
  logger.info("registerRequestListeners");
629
- if (!this.provider) {
861
+ if (!provider) {
630
862
  logger.error("Provider not found for request (signature, transaction) tracking");
631
863
  return;
632
864
  }
633
- if (((_a = Object.getOwnPropertyDescriptor(this.provider, "request")) === null || _a === void 0 ? void 0 : _a.writable) ===
634
- false) {
635
- logger.warn("Provider.request is not writable");
865
+ // Check if the provider is already wrapped with our SDK's wrapper
866
+ var currentRequest = provider.request;
867
+ if (this.isProviderAlreadyWrapped(provider, currentRequest)) {
868
+ logger.info("Provider already wrapped with our SDK; skipping request wrapping.");
636
869
  return;
637
870
  }
638
- var request = this.provider.request.bind(this.provider);
639
- this.provider.request = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
640
- var response_1, error_1, transactionHash_1, error_2;
871
+ var request = provider.request.bind(provider);
872
+ var wrappedRequest = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
873
+ var capturedChainId_1, _c, response_1, error_3, rpcError, transactionHash_1, error_4, rpcError;
641
874
  var _this = this;
642
875
  var method = _b.method, params = _b.params;
643
- return __generator(this, function (_c) {
644
- switch (_c.label) {
876
+ return __generator(this, function (_d) {
877
+ switch (_d.label) {
645
878
  case 0:
646
879
  if (!(Array.isArray(params) &&
647
- ["eth_signTypedData_v4", "personal_sign"].includes(method))) return [3 /*break*/, 4];
880
+ ["eth_signTypedData_v4", "personal_sign"].includes(method))) return [3 /*break*/, 6];
881
+ _c = this.currentChainId;
882
+ if (_c) return [3 /*break*/, 2];
883
+ return [4 /*yield*/, this.getCurrentChainId(provider)];
884
+ case 1:
885
+ _c = (_d.sent());
886
+ _d.label = 2;
887
+ case 2:
888
+ capturedChainId_1 = _c;
648
889
  // Fire-and-forget tracking
649
890
  (function () { return __awaiter(_this, void 0, void 0, function () {
650
891
  return __generator(this, function (_a) {
651
892
  try {
652
- this.signature(__assign({ status: SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params)));
893
+ this.signature(__assign({ status: SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1)));
653
894
  }
654
895
  catch (e) {
655
896
  logger.error("Formo: Failed to track signature request", e);
@@ -657,127 +898,139 @@ var FormoAnalytics = /** @class */ (function () {
657
898
  return [2 /*return*/];
658
899
  });
659
900
  }); })();
660
- _c.label = 1;
661
- case 1:
662
- _c.trys.push([1, 3, , 4]);
901
+ _d.label = 3;
902
+ case 3:
903
+ _d.trys.push([3, 5, , 6]);
663
904
  return [4 /*yield*/, request({ method: method, params: params })];
664
- case 2:
665
- response_1 = (_c.sent());
666
- (function () { return __awaiter(_this, void 0, void 0, function () {
667
- return __generator(this, function (_a) {
668
- try {
669
- if (response_1) {
670
- this.signature(__assign({ status: SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1)));
905
+ case 4:
906
+ response_1 = (_d.sent());
907
+ // Track signature confirmation only for truthy responses
908
+ if (response_1) {
909
+ (function () { return __awaiter(_this, void 0, void 0, function () {
910
+ return __generator(this, function (_a) {
911
+ try {
912
+ this.signature(__assign({ status: SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1)));
671
913
  }
672
- }
673
- catch (e) {
674
- logger.error("Formo: Failed to track signature confirmation", e);
675
- }
676
- return [2 /*return*/];
677
- });
678
- }); })();
914
+ catch (e) {
915
+ logger.error("Formo: Failed to track signature confirmation", e);
916
+ }
917
+ return [2 /*return*/];
918
+ });
919
+ }); })();
920
+ }
679
921
  return [2 /*return*/, response_1];
680
- case 3:
681
- error_1 = _c.sent();
682
- (function () { return __awaiter(_this, void 0, void 0, function () {
683
- var rpcError;
684
- return __generator(this, function (_a) {
685
- try {
686
- rpcError = error_1;
687
- if (rpcError && (rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
688
- this.signature(__assign({ status: SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params)));
922
+ case 5:
923
+ error_3 = _d.sent();
924
+ rpcError = error_3;
925
+ if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
926
+ // Use the already cast rpcError to avoid duplication
927
+ (function () { return __awaiter(_this, void 0, void 0, function () {
928
+ return __generator(this, function (_a) {
929
+ try {
930
+ this.signature(__assign({ status: SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1)));
689
931
  }
690
- }
691
- catch (e) {
692
- logger.error("Formo: Failed to track signature rejection", e);
693
- }
694
- return [2 /*return*/];
695
- });
696
- }); })();
697
- throw error_1;
698
- case 4:
932
+ catch (e) {
933
+ logger.error("Formo: Failed to track signature rejection", e);
934
+ }
935
+ return [2 /*return*/];
936
+ });
937
+ }); })();
938
+ }
939
+ throw error_3;
940
+ case 6:
699
941
  if (!(Array.isArray(params) &&
700
942
  method === "eth_sendTransaction" &&
701
- params[0])) return [3 /*break*/, 8];
943
+ params[0])) return [3 /*break*/, 10];
702
944
  (function () { return __awaiter(_this, void 0, void 0, function () {
703
- var payload, e_3;
945
+ var payload, e_4;
704
946
  return __generator(this, function (_a) {
705
947
  switch (_a.label) {
706
948
  case 0:
707
949
  _a.trys.push([0, 2, , 3]);
708
- return [4 /*yield*/, this.buildTransactionEventPayload(params)];
950
+ return [4 /*yield*/, this.buildTransactionEventPayload(params, provider)];
709
951
  case 1:
710
952
  payload = _a.sent();
711
953
  this.transaction(__assign({ status: TransactionStatus.STARTED }, payload));
712
954
  return [3 /*break*/, 3];
713
955
  case 2:
714
- e_3 = _a.sent();
715
- logger.error("Formo: Failed to track transaction start", e_3);
956
+ e_4 = _a.sent();
957
+ logger.error("Formo: Failed to track transaction start", e_4);
716
958
  return [3 /*break*/, 3];
717
959
  case 3: return [2 /*return*/];
718
960
  }
719
961
  });
720
962
  }); })();
721
- _c.label = 5;
722
- case 5:
723
- _c.trys.push([5, 7, , 8]);
963
+ _d.label = 7;
964
+ case 7:
965
+ _d.trys.push([7, 9, , 10]);
724
966
  return [4 /*yield*/, request({
725
967
  method: method,
726
968
  params: params,
727
969
  })];
728
- case 6:
729
- transactionHash_1 = (_c.sent());
970
+ case 8:
971
+ transactionHash_1 = (_d.sent());
730
972
  (function () { return __awaiter(_this, void 0, void 0, function () {
731
- var payload, e_4;
973
+ var payload, e_5;
732
974
  return __generator(this, function (_a) {
733
975
  switch (_a.label) {
734
976
  case 0:
735
977
  _a.trys.push([0, 2, , 3]);
736
- return [4 /*yield*/, this.buildTransactionEventPayload(params)];
978
+ return [4 /*yield*/, this.buildTransactionEventPayload(params, provider)];
737
979
  case 1:
738
980
  payload = _a.sent();
739
981
  this.transaction(__assign(__assign({ status: TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }));
740
982
  // Start async polling for transaction receipt
741
- this.pollTransactionReceipt(transactionHash_1, payload);
983
+ this.pollTransactionReceipt(provider, transactionHash_1, payload);
742
984
  return [3 /*break*/, 3];
743
985
  case 2:
744
- e_4 = _a.sent();
745
- logger.error("Formo: Failed to track transaction broadcast", e_4);
986
+ e_5 = _a.sent();
987
+ logger.error("Formo: Failed to track transaction broadcast", e_5);
746
988
  return [3 /*break*/, 3];
747
989
  case 3: return [2 /*return*/];
748
990
  }
749
991
  });
750
992
  }); })();
751
993
  return [2 /*return*/, transactionHash_1];
752
- case 7:
753
- error_2 = _c.sent();
754
- (function () { return __awaiter(_this, void 0, void 0, function () {
755
- var rpcError, payload, e_5;
756
- return __generator(this, function (_a) {
757
- switch (_a.label) {
758
- case 0:
759
- _a.trys.push([0, 3, , 4]);
760
- rpcError = error_2;
761
- if (!(rpcError && (rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001)) return [3 /*break*/, 2];
762
- return [4 /*yield*/, this.buildTransactionEventPayload(params)];
763
- case 1:
764
- payload = _a.sent();
765
- this.transaction(__assign({ status: TransactionStatus.REJECTED }, payload));
766
- _a.label = 2;
767
- case 2: return [3 /*break*/, 4];
768
- case 3:
769
- e_5 = _a.sent();
770
- logger.error("Formo: Failed to track transaction rejection", e_5);
771
- return [3 /*break*/, 4];
772
- case 4: return [2 /*return*/];
773
- }
774
- });
775
- }); })();
776
- throw error_2;
777
- case 8: return [2 /*return*/, request({ method: method, params: params })];
994
+ case 9:
995
+ error_4 = _d.sent();
996
+ rpcError = error_4;
997
+ if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
998
+ // Use the already cast rpcError to avoid duplication
999
+ (function () { return __awaiter(_this, void 0, void 0, function () {
1000
+ var payload, e_6;
1001
+ return __generator(this, function (_a) {
1002
+ switch (_a.label) {
1003
+ case 0:
1004
+ _a.trys.push([0, 2, , 3]);
1005
+ return [4 /*yield*/, this.buildTransactionEventPayload(params, provider)];
1006
+ case 1:
1007
+ payload = _a.sent();
1008
+ this.transaction(__assign({ status: TransactionStatus.REJECTED }, payload));
1009
+ return [3 /*break*/, 3];
1010
+ case 2:
1011
+ e_6 = _a.sent();
1012
+ logger.error("Formo: Failed to track transaction rejection", e_6);
1013
+ return [3 /*break*/, 3];
1014
+ case 3: return [2 /*return*/];
1015
+ }
1016
+ });
1017
+ }); })();
1018
+ }
1019
+ throw error_4;
1020
+ case 10: return [2 /*return*/, request({ method: method, params: params })];
778
1021
  }
779
1022
  });
780
1023
  }); };
1024
+ // Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
1025
+ wrappedRequest[WRAPPED_REQUEST_SYMBOL] = true;
1026
+ provider[WRAPPED_REQUEST_REF_SYMBOL] = wrappedRequest;
1027
+ try {
1028
+ // Attempt to assign the wrapped request function (rely on try-catch for mutability errors)
1029
+ provider.request = wrappedRequest;
1030
+ }
1031
+ catch (e) {
1032
+ logger.warn("Failed to wrap provider.request; skipping", e);
1033
+ }
781
1034
  };
782
1035
  FormoAnalytics.prototype.onLocationChange = function () {
783
1036
  return __awaiter(this, void 0, void 0, function () {
@@ -831,15 +1084,29 @@ var FormoAnalytics = /** @class */ (function () {
831
1084
  logger.info("Track page hit: Skipping event due to tracking configuration");
832
1085
  return [2 /*return*/];
833
1086
  }
834
- setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
835
- return __generator(this, function (_a) {
836
- this.trackEvent(EventType.PAGE, {
837
- category: category,
838
- name: name,
839
- }, properties, context, callback);
840
- return [2 /*return*/];
841
- });
842
- }); }, 300);
1087
+ setTimeout(function () {
1088
+ (function () { return __awaiter(_this, void 0, void 0, function () {
1089
+ var e_7;
1090
+ return __generator(this, function (_a) {
1091
+ switch (_a.label) {
1092
+ case 0:
1093
+ _a.trys.push([0, 2, , 3]);
1094
+ return [4 /*yield*/, this.trackEvent(EventType.PAGE, {
1095
+ category: category,
1096
+ name: name,
1097
+ }, properties, context, callback)];
1098
+ case 1:
1099
+ _a.sent();
1100
+ return [3 /*break*/, 3];
1101
+ case 2:
1102
+ e_7 = _a.sent();
1103
+ logger.error("Formo: Failed to track page hit", e_7);
1104
+ return [3 /*break*/, 3];
1105
+ case 3: return [2 /*return*/];
1106
+ }
1107
+ });
1108
+ }); })();
1109
+ }, 300);
843
1110
  return [2 /*return*/];
844
1111
  });
845
1112
  });
@@ -904,24 +1171,167 @@ var FormoAnalytics = /** @class */ (function () {
904
1171
  /*
905
1172
  Utility functions
906
1173
  */
1174
+ /**
1175
+ * Get provider information for a given provider
1176
+ * @param provider The provider to get info for
1177
+ * @returns Provider information
1178
+ */
1179
+ FormoAnalytics.prototype.getProviderInfo = function (provider) {
1180
+ // First check if provider is in our EIP-6963 providers list
1181
+ var eip6963Provider = this._providers.find(function (p) { return p.provider === provider; });
1182
+ if (eip6963Provider) {
1183
+ return {
1184
+ name: eip6963Provider.info.name,
1185
+ rdns: eip6963Provider.info.rdns
1186
+ };
1187
+ }
1188
+ // Fallback to injected provider detection
1189
+ var injectedInfo = this.detectInjectedProviderInfo(provider);
1190
+ return {
1191
+ name: injectedInfo.name,
1192
+ rdns: injectedInfo.rdns
1193
+ };
1194
+ };
1195
+ /**
1196
+ * Attempts to detect information about an injected provider
1197
+ * @param provider The injected provider to analyze
1198
+ * @returns Provider information with fallback values
1199
+ */
1200
+ FormoAnalytics.prototype.detectInjectedProviderInfo = function (provider) {
1201
+ // Try to detect provider type from common properties
1202
+ var name = 'Injected Provider';
1203
+ var rdns = 'io.injected.provider';
1204
+ // Use WalletProviderFlags interface for type safety
1205
+ var flags = provider;
1206
+ // Check if it's MetaMask
1207
+ if (flags.isMetaMask) {
1208
+ name = 'MetaMask';
1209
+ rdns = 'io.metamask';
1210
+ }
1211
+ // Check if it's Coinbase Wallet
1212
+ else if (flags.isCoinbaseWallet) {
1213
+ name = 'Coinbase Wallet';
1214
+ rdns = 'com.coinbase.wallet';
1215
+ }
1216
+ // Check if it's WalletConnect
1217
+ else if (flags.isWalletConnect) {
1218
+ name = 'WalletConnect';
1219
+ rdns = 'com.walletconnect';
1220
+ }
1221
+ // Check if it's Trust Wallet
1222
+ else if (flags.isTrust) {
1223
+ name = 'Trust Wallet';
1224
+ rdns = 'com.trustwallet';
1225
+ }
1226
+ // Check if it's Brave Wallet
1227
+ else if (flags.isBraveWallet) {
1228
+ name = 'Brave Wallet';
1229
+ rdns = 'com.brave.wallet';
1230
+ }
1231
+ // Check if it's Phantom
1232
+ else if (flags.isPhantom) {
1233
+ name = 'Phantom';
1234
+ rdns = 'app.phantom';
1235
+ }
1236
+ return {
1237
+ name: name,
1238
+ rdns: rdns,
1239
+ uuid: "injected-".concat(rdns.replace(/[^a-zA-Z0-9]/g, '-')),
1240
+ icon: DEFAULT_PROVIDER_ICON
1241
+ };
1242
+ };
907
1243
  FormoAnalytics.prototype.getProviders = function () {
908
1244
  return __awaiter(this, void 0, void 0, function () {
909
- var store, providers;
1245
+ var store, providers, injected_1, injectedProviderInfo, injectedDetail, uniqueProviders, _i, uniqueProviders_1, detail;
910
1246
  var _this = this;
911
1247
  return __generator(this, function (_a) {
912
1248
  store = createStore();
913
1249
  providers = store.getProviders();
914
1250
  store.subscribe(function (providerDetails) {
915
1251
  providers = providerDetails;
916
- _this._providers = providers;
1252
+ // Process newly added providers with proper deduplication
1253
+ var newlyAddedDetails = providerDetails.filter(function (detail) {
1254
+ var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
1255
+ return provider && !_this._seenProviders.has(provider);
1256
+ });
1257
+ // Add new providers to the array without overwriting existing ones
1258
+ for (var _i = 0, newlyAddedDetails_1 = newlyAddedDetails; _i < newlyAddedDetails_1.length; _i++) {
1259
+ var detail = newlyAddedDetails_1[_i];
1260
+ _this.safeAddProviderDetail(detail);
1261
+ }
1262
+ // Track listeners for newly discovered providers only
1263
+ var newDetails = providerDetails.filter(function (detail) {
1264
+ var p = detail === null || detail === void 0 ? void 0 : detail.provider;
1265
+ return !!p && !_this._trackedProviders.has(p);
1266
+ });
1267
+ if (newDetails.length > 0) {
1268
+ _this.trackProviders(newDetails);
1269
+ // Detect newly discovered wallets (session de-dupes) with error handling
1270
+ (function () { return __awaiter(_this, void 0, void 0, function () {
1271
+ var e_8;
1272
+ return __generator(this, function (_a) {
1273
+ switch (_a.label) {
1274
+ case 0:
1275
+ _a.trys.push([0, 2, , 3]);
1276
+ return [4 /*yield*/, this.detectWallets(newDetails)];
1277
+ case 1:
1278
+ _a.sent();
1279
+ return [3 /*break*/, 3];
1280
+ case 2:
1281
+ e_8 = _a.sent();
1282
+ logger.error("Formo: Failed to detect wallets", e_8);
1283
+ return [3 /*break*/, 3];
1284
+ case 3: return [2 /*return*/];
1285
+ }
1286
+ });
1287
+ }); })();
1288
+ }
1289
+ // Clean up providers that are no longer available
1290
+ _this.cleanupUnavailableProviders();
917
1291
  });
918
1292
  // Fallback to injected provider if no providers are found
919
1293
  if (providers.length === 0) {
920
- this._providers = (window === null || window === void 0 ? void 0 : window.ethereum) ? [window.ethereum] : [];
1294
+ injected_1 = typeof window !== 'undefined' ? window.ethereum : undefined;
1295
+ if (injected_1) {
1296
+ // If we have already detected and cached the injected provider, and it's the same instance, return the cached result
1297
+ if (this._injectedProviderDetail &&
1298
+ this._injectedProviderDetail.provider === injected_1) {
1299
+ // Ensure it's tracked
1300
+ if (!this._trackedProviders.has(injected_1)) {
1301
+ this.trackProvider(injected_1);
1302
+ }
1303
+ // Merge with existing providers instead of overwriting
1304
+ if (!this._providers.some(function (existing) { return existing.provider === injected_1; })) {
1305
+ this._providers = __spreadArray(__spreadArray([], this._providers, true), [this._injectedProviderDetail], false);
1306
+ }
1307
+ return [2 /*return*/, this._providers];
1308
+ }
1309
+ // Re-check if the injected provider is already tracked just before tracking
1310
+ if (!this._trackedProviders.has(injected_1)) {
1311
+ this.trackProvider(injected_1);
1312
+ }
1313
+ injectedProviderInfo = this.detectInjectedProviderInfo(injected_1);
1314
+ injectedDetail = {
1315
+ provider: injected_1,
1316
+ info: injectedProviderInfo
1317
+ };
1318
+ // Cache the detected injected provider detail
1319
+ this._injectedProviderDetail = injectedDetail;
1320
+ // Merge with existing providers instead of overwriting
1321
+ this.safeAddProviderDetail(injectedDetail);
1322
+ }
921
1323
  return [2 /*return*/, this._providers];
922
1324
  }
923
- this._providers = providers;
924
- return [2 /*return*/, providers];
1325
+ uniqueProviders = providers.filter(function (detail) {
1326
+ var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
1327
+ return provider && !_this._seenProviders.has(provider);
1328
+ });
1329
+ // Add to seen providers and instances, ensuring no duplicates in _providers
1330
+ for (_i = 0, uniqueProviders_1 = uniqueProviders; _i < uniqueProviders_1.length; _i++) {
1331
+ detail = uniqueProviders_1[_i];
1332
+ this.safeAddProviderDetail(detail);
1333
+ }
1334
+ return [2 /*return*/, this._providers];
925
1335
  });
926
1336
  });
927
1337
  };
@@ -934,16 +1344,16 @@ var FormoAnalytics = /** @class */ (function () {
934
1344
  });
935
1345
  FormoAnalytics.prototype.detectWallets = function (providers) {
936
1346
  return __awaiter(this, void 0, void 0, function () {
937
- var _i, providers_1, eip6963ProviderDetail, err_2;
1347
+ var _i, providers_2, eip6963ProviderDetail, err_2;
938
1348
  return __generator(this, function (_a) {
939
1349
  switch (_a.label) {
940
1350
  case 0:
941
1351
  _a.trys.push([0, 5, , 6]);
942
- _i = 0, providers_1 = providers;
1352
+ _i = 0, providers_2 = providers;
943
1353
  _a.label = 1;
944
1354
  case 1:
945
- if (!(_i < providers_1.length)) return [3 /*break*/, 4];
946
- eip6963ProviderDetail = providers_1[_i];
1355
+ if (!(_i < providers_2.length)) return [3 /*break*/, 4];
1356
+ eip6963ProviderDetail = providers_2[_i];
947
1357
  return [4 /*yield*/, this.detect({
948
1358
  providerName: eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.info.name,
949
1359
  rdns: eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.info.rdns,
@@ -973,7 +1383,7 @@ var FormoAnalytics = /** @class */ (function () {
973
1383
  });
974
1384
  FormoAnalytics.prototype.getAddress = function (provider) {
975
1385
  return __awaiter(this, void 0, void 0, function () {
976
- var p, accounts, validAddress, err_3;
1386
+ var p, accounts, err_3, code;
977
1387
  return __generator(this, function (_a) {
978
1388
  switch (_a.label) {
979
1389
  case 0:
@@ -991,15 +1401,15 @@ var FormoAnalytics = /** @class */ (function () {
991
1401
  case 2:
992
1402
  accounts = _a.sent();
993
1403
  if (accounts && accounts.length > 0) {
994
- validAddress = getValidAddress(accounts[0]);
995
- if (validAddress) {
996
- return [2 /*return*/, toChecksumAddress(validAddress)];
997
- }
1404
+ return [2 /*return*/, this.validateAndChecksumAddress(accounts[0]) || null];
998
1405
  }
999
1406
  return [3 /*break*/, 4];
1000
1407
  case 3:
1001
1408
  err_3 = _a.sent();
1002
- logger.error("Failed to fetch accounts from provider:", err_3);
1409
+ code = err_3 === null || err_3 === void 0 ? void 0 : err_3.code;
1410
+ if (code !== 4001) {
1411
+ logger.error("FormoAnalytics::getAccounts: eth_accounts threw an error", err_3);
1412
+ }
1003
1413
  return [2 /*return*/, null];
1004
1414
  case 4: return [2 /*return*/, null];
1005
1415
  }
@@ -1008,7 +1418,8 @@ var FormoAnalytics = /** @class */ (function () {
1008
1418
  };
1009
1419
  FormoAnalytics.prototype.getAccounts = function (provider) {
1010
1420
  return __awaiter(this, void 0, void 0, function () {
1011
- var p, res, err_4;
1421
+ var p, res, err_4, code;
1422
+ var _this = this;
1012
1423
  return __generator(this, function (_a) {
1013
1424
  switch (_a.label) {
1014
1425
  case 0:
@@ -1024,12 +1435,12 @@ var FormoAnalytics = /** @class */ (function () {
1024
1435
  if (!res || res.length === 0)
1025
1436
  return [2 /*return*/, null];
1026
1437
  return [2 /*return*/, res
1027
- .map(function (e) { return getValidAddress(e); })
1028
- .filter(function (e) { return e !== null; })
1029
- .map(toChecksumAddress)];
1438
+ .map(function (e) { return _this.validateAndChecksumAddress(e); })
1439
+ .filter(function (e) { return e !== undefined; })];
1030
1440
  case 3:
1031
1441
  err_4 = _a.sent();
1032
- if (err_4.code !== 4001) {
1442
+ code = err_4 === null || err_4 === void 0 ? void 0 : err_4.code;
1443
+ if (code !== 4001) {
1033
1444
  logger.error("FormoAnalytics::getAccounts: eth_accounts threw an error", err_4);
1034
1445
  }
1035
1446
  return [2 /*return*/, null];
@@ -1038,31 +1449,32 @@ var FormoAnalytics = /** @class */ (function () {
1038
1449
  });
1039
1450
  });
1040
1451
  };
1041
- FormoAnalytics.prototype.getCurrentChainId = function () {
1452
+ FormoAnalytics.prototype.getCurrentChainId = function (provider) {
1042
1453
  return __awaiter(this, void 0, void 0, function () {
1043
- var chainIdHex, err_5;
1044
- var _a;
1045
- return __generator(this, function (_b) {
1046
- switch (_b.label) {
1454
+ var p, chainIdHex, err_5;
1455
+ return __generator(this, function (_a) {
1456
+ switch (_a.label) {
1047
1457
  case 0:
1048
- if (!this.provider) {
1458
+ p = provider || this.provider;
1459
+ if (!p) {
1049
1460
  logger.error("Provider not set for chain ID");
1461
+ return [2 /*return*/, 0];
1050
1462
  }
1051
- _b.label = 1;
1463
+ _a.label = 1;
1052
1464
  case 1:
1053
- _b.trys.push([1, 3, , 4]);
1054
- return [4 /*yield*/, ((_a = this.provider) === null || _a === void 0 ? void 0 : _a.request({
1465
+ _a.trys.push([1, 3, , 4]);
1466
+ return [4 /*yield*/, p.request({
1055
1467
  method: "eth_chainId",
1056
- }))];
1468
+ })];
1057
1469
  case 2:
1058
- chainIdHex = _b.sent();
1470
+ chainIdHex = _a.sent();
1059
1471
  if (!chainIdHex) {
1060
1472
  logger.info("Chain id not found");
1061
1473
  return [2 /*return*/, 0];
1062
1474
  }
1063
1475
  return [2 /*return*/, parseChainId(chainIdHex)];
1064
1476
  case 3:
1065
- err_5 = _b.sent();
1477
+ err_5 = _a.sent();
1066
1478
  logger.error("eth_chainId threw an error:", err_5);
1067
1479
  return [2 /*return*/, 0];
1068
1480
  case 4: return [2 /*return*/];
@@ -1070,17 +1482,18 @@ var FormoAnalytics = /** @class */ (function () {
1070
1482
  });
1071
1483
  });
1072
1484
  };
1073
- FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params, response) {
1485
+ FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params, response, chainId) {
1486
+ var _a;
1074
1487
  var rawAddress = method === "personal_sign"
1075
1488
  ? params[1]
1076
1489
  : params[0];
1077
- var validAddress = getValidAddress(rawAddress);
1490
+ var validAddress = this.validateAndChecksumAddress(rawAddress);
1078
1491
  if (!validAddress) {
1079
1492
  throw new Error("Invalid address in signature payload: ".concat(rawAddress));
1080
1493
  }
1081
1494
  var basePayload = {
1082
- chainId: this.currentChainId,
1083
- address: toChecksumAddress(validAddress),
1495
+ chainId: (_a = chainId !== null && chainId !== void 0 ? chainId : this.currentChainId) !== null && _a !== void 0 ? _a : undefined,
1496
+ address: validAddress,
1084
1497
  };
1085
1498
  if (method === "personal_sign") {
1086
1499
  var message = Buffer.from(params[0].slice(2), "hex").toString("utf8");
@@ -1088,7 +1501,7 @@ var FormoAnalytics = /** @class */ (function () {
1088
1501
  }
1089
1502
  return __assign(__assign(__assign({}, basePayload), { message: params[1] }), (response ? { signatureHash: response } : {}));
1090
1503
  };
1091
- FormoAnalytics.prototype.buildTransactionEventPayload = function (params) {
1504
+ FormoAnalytics.prototype.buildTransactionEventPayload = function (params, provider) {
1092
1505
  return __awaiter(this, void 0, void 0, function () {
1093
1506
  var _a, data, from, to, value, validAddress, _b;
1094
1507
  var _c;
@@ -1096,20 +1509,20 @@ var FormoAnalytics = /** @class */ (function () {
1096
1509
  switch (_d.label) {
1097
1510
  case 0:
1098
1511
  _a = params[0], data = _a.data, from = _a.from, to = _a.to, value = _a.value;
1099
- validAddress = getValidAddress(from);
1512
+ validAddress = this.validateAndChecksumAddress(from);
1100
1513
  if (!validAddress) {
1101
1514
  throw new Error("Invalid address in transaction payload: ".concat(from));
1102
1515
  }
1103
1516
  _c = {};
1104
1517
  _b = this.currentChainId;
1105
1518
  if (_b) return [3 /*break*/, 2];
1106
- return [4 /*yield*/, this.getCurrentChainId()];
1519
+ return [4 /*yield*/, this.getCurrentChainId(provider)];
1107
1520
  case 1:
1108
1521
  _b = (_d.sent());
1109
1522
  _d.label = 2;
1110
1523
  case 2: return [2 /*return*/, (_c.chainId = _b,
1111
1524
  _c.data = data,
1112
- _c.address = toChecksumAddress(validAddress),
1525
+ _c.address = validAddress,
1113
1526
  _c.to = to,
1114
1527
  _c.value = value,
1115
1528
  _c)];
@@ -1120,19 +1533,18 @@ var FormoAnalytics = /** @class */ (function () {
1120
1533
  /**
1121
1534
  * Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
1122
1535
  */
1123
- FormoAnalytics.prototype.pollTransactionReceipt = function (transactionHash_2, payload_1) {
1124
- return __awaiter(this, arguments, void 0, function (transactionHash, payload, maxAttempts, intervalMs) {
1125
- var attempts, provider, poll;
1536
+ FormoAnalytics.prototype.pollTransactionReceipt = function (provider_1, transactionHash_2, payload_1) {
1537
+ return __awaiter(this, arguments, void 0, function (provider, transactionHash, payload, maxAttempts, intervalMs) {
1538
+ var attempts, poll;
1126
1539
  var _this = this;
1127
1540
  if (maxAttempts === void 0) { maxAttempts = 10; }
1128
1541
  if (intervalMs === void 0) { intervalMs = 3000; }
1129
1542
  return __generator(this, function (_a) {
1130
1543
  attempts = 0;
1131
- provider = this.provider;
1132
1544
  if (!provider)
1133
1545
  return [2 /*return*/];
1134
1546
  poll = function () { return __awaiter(_this, void 0, void 0, function () {
1135
- var receipt, e_6;
1547
+ var receipt, e_9;
1136
1548
  return __generator(this, function (_a) {
1137
1549
  switch (_a.label) {
1138
1550
  case 0:
@@ -1156,8 +1568,8 @@ var FormoAnalytics = /** @class */ (function () {
1156
1568
  }
1157
1569
  return [3 /*break*/, 3];
1158
1570
  case 2:
1159
- e_6 = _a.sent();
1160
- logger.error("Error polling transaction receipt", e_6);
1571
+ e_9 = _a.sent();
1572
+ logger.error("Error polling transaction receipt", e_9);
1161
1573
  return [3 /*break*/, 3];
1162
1574
  case 3:
1163
1575
  attempts++;
@@ -1173,6 +1585,122 @@ var FormoAnalytics = /** @class */ (function () {
1173
1585
  });
1174
1586
  });
1175
1587
  };
1588
+ FormoAnalytics.prototype.removeProviderListeners = function (provider) {
1589
+ var listeners = this._providerListenersMap.get(provider);
1590
+ if (!listeners)
1591
+ return;
1592
+ for (var _i = 0, _a = Object.entries(listeners); _i < _a.length; _i++) {
1593
+ var _b = _a[_i], event_1 = _b[0], fn = _b[1];
1594
+ try {
1595
+ provider.removeListener(event_1, fn);
1596
+ }
1597
+ catch (e) {
1598
+ logger.warn("Failed to remove listener for ".concat(String(event_1)), e);
1599
+ }
1600
+ }
1601
+ this._providerListenersMap.delete(provider);
1602
+ };
1603
+ // Explicitly untrack a provider: remove listeners, clear wrapper flag and tracking
1604
+ FormoAnalytics.prototype.untrackProvider = function (provider) {
1605
+ try {
1606
+ this.removeProviderListeners(provider);
1607
+ this._trackedProviders.delete(provider);
1608
+ if (this._provider === provider) {
1609
+ this._provider = undefined;
1610
+ }
1611
+ }
1612
+ catch (e) {
1613
+ logger.warn("Failed to untrack provider", e);
1614
+ }
1615
+ };
1616
+ // Debug/monitoring helpers
1617
+ FormoAnalytics.prototype.getTrackedProvidersCount = function () {
1618
+ return this._trackedProviders.size;
1619
+ };
1620
+ /**
1621
+ * Get current provider state for debugging
1622
+ * @returns Object containing current provider state information
1623
+ */
1624
+ FormoAnalytics.prototype.getProviderState = function () {
1625
+ return {
1626
+ totalProviders: this._providers.length,
1627
+ trackedProviders: this._trackedProviders.size,
1628
+ seenProviders: this._seenProviders.size,
1629
+ activeProvider: !!this._provider,
1630
+ };
1631
+ };
1632
+ /**
1633
+ * Clean up providers that are no longer available
1634
+ * This helps maintain consistent state and prevents memory leaks
1635
+ */
1636
+ FormoAnalytics.prototype.cleanupUnavailableProviders = function () {
1637
+ // Remove providers that are no longer in the current providers list
1638
+ var currentProviderInstances = new Set(this._providers.map(function (detail) { return detail.provider; }));
1639
+ for (var _i = 0, _a = Array.from(this._trackedProviders); _i < _a.length; _i++) {
1640
+ var provider = _a[_i];
1641
+ if (!currentProviderInstances.has(provider)) {
1642
+ logger.info("Cleaning up unavailable provider: ".concat(provider.constructor.name));
1643
+ this.untrackProvider(provider);
1644
+ }
1645
+ }
1646
+ };
1647
+ /**
1648
+ * Helper method to check if a provider is already wrapped
1649
+ * @param provider The provider to check
1650
+ * @param currentRequest The current request function
1651
+ * @returns true if the provider is already wrapped
1652
+ */
1653
+ FormoAnalytics.prototype.isProviderAlreadyWrapped = function (provider, currentRequest) {
1654
+ return !!(currentRequest &&
1655
+ typeof currentRequest === 'function' &&
1656
+ currentRequest[WRAPPED_REQUEST_SYMBOL] &&
1657
+ provider[WRAPPED_REQUEST_REF_SYMBOL] === currentRequest);
1658
+ };
1659
+ /**
1660
+ * Handle provider mismatch by switching to the new provider and invalidating old tokens
1661
+ * @param provider The new provider to switch to
1662
+ */
1663
+ FormoAnalytics.prototype.handleProviderMismatch = function (provider) {
1664
+ // If this is a different provider, allow the switch
1665
+ if (this._provider) {
1666
+ // Clear any provider-specific state when switching
1667
+ this.currentChainId = undefined;
1668
+ this.currentAddress = undefined;
1669
+ }
1670
+ this._provider = provider;
1671
+ };
1672
+ /**
1673
+ * Helper method to validate and checksum an address
1674
+ * @param address The address to validate and checksum
1675
+ * @returns The checksummed address or undefined if invalid
1676
+ */
1677
+ FormoAnalytics.prototype.validateAndChecksumAddress = function (address) {
1678
+ var validAddress = getValidAddress(address);
1679
+ return validAddress ? toChecksumAddress(validAddress) : undefined;
1680
+ };
1681
+ /**
1682
+ * Helper method to safely add a provider detail to _providers array, ensuring no duplicates
1683
+ * @param detail The provider detail to add
1684
+ * @returns true if the provider was added, false if it was already present
1685
+ */
1686
+ FormoAnalytics.prototype.safeAddProviderDetail = function (detail) {
1687
+ var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
1688
+ if (!provider)
1689
+ return false;
1690
+ // Check if provider already exists in _providers array
1691
+ var alreadyExists = this._providers.some(function (existing) { return existing.provider === provider; });
1692
+ if (!alreadyExists) {
1693
+ // Add to providers array and mark as seen
1694
+ this._providers = __spreadArray(__spreadArray([], this._providers, true), [detail], false);
1695
+ this._seenProviders.add(provider);
1696
+ return true;
1697
+ }
1698
+ else {
1699
+ // Ensure provider is marked as seen even if it already exists in _providers
1700
+ this._seenProviders.add(provider);
1701
+ return false;
1702
+ }
1703
+ };
1176
1704
  return FormoAnalytics;
1177
1705
  }());
1178
1706
  export { FormoAnalytics };