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