@formo/analytics 1.38.1 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +10 -1
  2. package/dist/cjs/src/FormoAnalytics.js +20 -5
  3. package/dist/cjs/src/FormoAnalyticsProvider.js +20 -0
  4. package/dist/cjs/src/evm/EvmEventTracker.d.ts +100 -9
  5. package/dist/cjs/src/evm/EvmEventTracker.js +357 -19
  6. package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +34 -2
  7. package/dist/cjs/src/evm/EvmProviderRegistry.js +61 -4
  8. package/dist/cjs/src/evm/EvmRequestTracker.d.ts +9 -4
  9. package/dist/cjs/src/evm/EvmRequestTracker.js +26 -16
  10. package/dist/cjs/src/solana/SolanaManager.d.ts +37 -9
  11. package/dist/cjs/src/solana/SolanaManager.js +135 -20
  12. package/dist/cjs/src/solana/SolanaStoreHandler.d.ts +6 -0
  13. package/dist/cjs/src/solana/SolanaStoreHandler.js +17 -8
  14. package/dist/cjs/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
  15. package/dist/cjs/src/solana/SolanaWalletStandardRegistry.js +447 -0
  16. package/dist/cjs/src/solana/index.d.ts +10 -5
  17. package/dist/cjs/src/solana/index.js +12 -6
  18. package/dist/cjs/src/solana/storeTypes.d.ts +20 -1
  19. package/dist/cjs/src/solana/types.d.ts +32 -9
  20. package/dist/cjs/src/solana/types.js +15 -0
  21. package/dist/cjs/src/solana/walletStandardTypes.d.ts +54 -0
  22. package/dist/cjs/src/solana/walletStandardTypes.js +21 -0
  23. package/dist/cjs/src/types/base.d.ts +15 -5
  24. package/dist/cjs/src/version.d.ts +1 -1
  25. package/dist/cjs/src/version.js +1 -1
  26. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +30 -1
  27. package/dist/cjs/src/wagmi/WagmiEventHandler.js +80 -1
  28. package/dist/cjs/src/wagmi/types.d.ts +6 -0
  29. package/dist/cjs/src/wagmi/utils.js +9 -1
  30. package/dist/esm/src/FormoAnalytics.d.ts +10 -1
  31. package/dist/esm/src/FormoAnalytics.js +20 -5
  32. package/dist/esm/src/FormoAnalyticsProvider.js +20 -0
  33. package/dist/esm/src/evm/EvmEventTracker.d.ts +100 -9
  34. package/dist/esm/src/evm/EvmEventTracker.js +357 -19
  35. package/dist/esm/src/evm/EvmProviderRegistry.d.ts +34 -2
  36. package/dist/esm/src/evm/EvmProviderRegistry.js +61 -4
  37. package/dist/esm/src/evm/EvmRequestTracker.d.ts +9 -4
  38. package/dist/esm/src/evm/EvmRequestTracker.js +26 -16
  39. package/dist/esm/src/solana/SolanaManager.d.ts +37 -9
  40. package/dist/esm/src/solana/SolanaManager.js +135 -20
  41. package/dist/esm/src/solana/SolanaStoreHandler.d.ts +6 -0
  42. package/dist/esm/src/solana/SolanaStoreHandler.js +18 -9
  43. package/dist/esm/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
  44. package/dist/esm/src/solana/SolanaWalletStandardRegistry.js +444 -0
  45. package/dist/esm/src/solana/index.d.ts +10 -5
  46. package/dist/esm/src/solana/index.js +10 -5
  47. package/dist/esm/src/solana/storeTypes.d.ts +20 -1
  48. package/dist/esm/src/solana/types.d.ts +32 -9
  49. package/dist/esm/src/solana/types.js +14 -0
  50. package/dist/esm/src/solana/walletStandardTypes.d.ts +54 -0
  51. package/dist/esm/src/solana/walletStandardTypes.js +18 -0
  52. package/dist/esm/src/types/base.d.ts +15 -5
  53. package/dist/esm/src/version.d.ts +1 -1
  54. package/dist/esm/src/version.js +1 -1
  55. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +30 -1
  56. package/dist/esm/src/wagmi/WagmiEventHandler.js +80 -1
  57. package/dist/esm/src/wagmi/types.d.ts +6 -0
  58. package/dist/esm/src/wagmi/utils.js +9 -1
  59. package/dist/index.umd.min.js +1 -1
  60. package/package.json +2 -2
@@ -428,8 +428,17 @@ export declare class FormoAnalytics implements IFormoAnalytics {
428
428
  * gap. Lifecycle (connect/chain/disconnect) stays store-driven: only the
429
429
  * request wrapper installs here. Double counting is prevented in the
430
430
  * wrapper via `shouldSkipRequestCapture`.
431
+ *
432
+ * `attribution` resolves, live, the name and rdns of the connector this
433
+ * provider was wrapped for. Request-derived events are named from the
434
+ * registry, which for an unannounced provider falls back to flag
435
+ * sniffing; recording the connector's resolver here keeps them in
436
+ * agreement with the hook-driven events for that connector.
431
437
  */
432
- _wrapWagmiProvider(provider: EIP1193Provider, chainId?: number): boolean;
438
+ _wrapWagmiProvider(provider: EIP1193Provider, chainId?: number, attribution?: () => {
439
+ name: string;
440
+ rdns?: string;
441
+ } | undefined): boolean;
433
442
  /** INTERNAL. Chain updates for the fallback-wrapped provider. */
434
443
  _rememberWagmiProviderChain(provider: EIP1193Provider, chainId: number | undefined): void;
435
444
  registerProvider(provider: EIP1193Provider, info?: {
@@ -245,9 +245,12 @@ var FormoAnalytics = /** @class */ (function () {
245
245
  this.evmEvents.trackEIP1193Provider(provider);
246
246
  }
247
247
  }
248
- // Initialize Solana manager if Solana options are provided
249
- if (options.solana) {
250
- this.solanaManager = new SolanaManager_1.SolanaManager(this, options.solana);
248
+ // Solana wallets are discovered through the Wallet Standard
249
+ // unconditionally, the way EVM wallets are through EIP-6963: an app
250
+ // that never configures Solana still gets its connects. `solana: false`
251
+ // is the opt-out; an object adds framework-kit's store or a cluster.
252
+ if (options.solana !== false) {
253
+ this.solanaManager = new SolanaManager_1.SolanaManager(this, typeof options.solana === "object" ? options.solana : undefined);
251
254
  }
252
255
  this._currentUrl = window.location.href;
253
256
  // Seed currentAddress/currentChainId from the persisted snapshot before
@@ -1018,6 +1021,9 @@ var FormoAnalytics = /** @class */ (function () {
1018
1021
  // Drop anything already buffered so a pending timer/pagehide flush
1019
1022
  // cannot ship events after consent withdrawal.
1020
1023
  this.eventManager.clear();
1024
+ // Identity is purged below; registered sessions must be re-learned
1025
+ // on opt-in, and nothing else would retry an already-adopted one.
1026
+ this.evmEvents.markRegisteredAdoptionsPending();
1021
1027
  this.reset();
1022
1028
  logger_1.logger.info("Successfully opted out of tracking");
1023
1029
  };
@@ -1268,7 +1274,9 @@ var FormoAnalytics = /** @class */ (function () {
1268
1274
  */
1269
1275
  get: function () {
1270
1276
  if (!this.solanaManager) {
1271
- this.solanaManager = new SolanaManager_1.SolanaManager(this);
1277
+ // Only reachable after `solana: false` (or after cleanup). The host
1278
+ // opted out of discovery, so this manager serves the store path only.
1279
+ this.solanaManager = new SolanaManager_1.SolanaManager(this, undefined, false);
1272
1280
  }
1273
1281
  return this.solanaManager;
1274
1282
  },
@@ -1363,8 +1371,14 @@ var FormoAnalytics = /** @class */ (function () {
1363
1371
  * gap. Lifecycle (connect/chain/disconnect) stays store-driven: only the
1364
1372
  * request wrapper installs here. Double counting is prevented in the
1365
1373
  * wrapper via `shouldSkipRequestCapture`.
1374
+ *
1375
+ * `attribution` resolves, live, the name and rdns of the connector this
1376
+ * provider was wrapped for. Request-derived events are named from the
1377
+ * registry, which for an unannounced provider falls back to flag
1378
+ * sniffing; recording the connector's resolver here keeps them in
1379
+ * agreement with the hook-driven events for that connector.
1366
1380
  */
1367
- FormoAnalytics.prototype._wrapWagmiProvider = function (provider, chainId) {
1381
+ FormoAnalytics.prototype._wrapWagmiProvider = function (provider, chainId, attribution) {
1368
1382
  if (this.isCleanedUp || !(0, provider_1.isValidProvider)(provider))
1369
1383
  return false;
1370
1384
  try {
@@ -1374,6 +1388,7 @@ var FormoAnalytics = /** @class */ (function () {
1374
1388
  if (!this.evmRequests.registerRequestListeners(provider)) {
1375
1389
  return false;
1376
1390
  }
1391
+ this.evm.rememberAttribution(provider, attribution);
1377
1392
  if (chainId !== undefined) {
1378
1393
  this.evm.rememberChain(provider, chainId);
1379
1394
  }
@@ -72,6 +72,18 @@ var defaultContext = {
72
72
  hasOptedOutTracking: function () { return false; },
73
73
  };
74
74
  exports.FormoAnalyticsContext = (0, react_1.createContext)(defaultContext);
75
+ var optionObjectIds = new WeakMap();
76
+ var nextOptionObjectId = 1;
77
+ var optionObjectId = function (value) {
78
+ if (!value)
79
+ return undefined;
80
+ var id = optionObjectIds.get(value);
81
+ if (id === undefined) {
82
+ id = nextOptionObjectId++;
83
+ optionObjectIds.set(value, id);
84
+ }
85
+ return id;
86
+ };
75
87
  /**
76
88
  * A stable key over the serializable parts of Options. The provider effect
77
89
  * re-initialises the SDK when this key changes; anything that alters SDK
@@ -95,6 +107,14 @@ var computeOptionsKey = function (options) {
95
107
  logger: options.logger,
96
108
  referral: options.referral,
97
109
  evm: options.evm,
110
+ // `solana` is a boolean or an options object. A store is a live event
111
+ // source, so replacing it must reinitialize the SDK even when both the
112
+ // old and new options contain a store.
113
+ solana: typeof options.solana === "object" ? undefined : options.solana,
114
+ solanaStoreId: typeof options.solana === "object"
115
+ ? optionObjectId(options.solana.store)
116
+ : undefined,
117
+ solanaCluster: typeof options.solana === "object" ? options.solana.cluster : undefined,
98
118
  // For complex objects, just track their presence, not their content
99
119
  hasProvider: !!options.provider,
100
120
  hasWagmi: !!options.wagmi,
@@ -52,10 +52,91 @@ export declare class EvmEventTracker {
52
52
  * Announcement-driven cleanup must not touch them: they are never in an
53
53
  * announcement list, so "missing from the announcement" is their normal
54
54
  * state, not evidence of removal. A Set rather than a WeakSet because
55
- * suppressed adoptions retry from it; the registry holds these providers
56
- * strongly anyway, and untrack removes them.
55
+ * the corrected-detect pass iterates it; the registry's detail list
56
+ * holds these providers strongly for the instance's life anyway.
57
57
  */
58
58
  private externallyRegistered;
59
+ /**
60
+ * Registered providers whose session this SDK has NOT yet learned.
61
+ *
62
+ * Only these are retried on page hits and opt-in. Re-running adoption for
63
+ * every registered provider looked idempotent but was not: adoption is
64
+ * the same path a live `accountsChanged` takes, and that path treats a
65
+ * provider with a different address from the active one as a wallet
66
+ * switch. With two registered providers, or one registered next to a
67
+ * discovered wallet, every page hit emitted a disconnect and a connect
68
+ * that no user action caused.
69
+ *
70
+ * A provider enters at registration (it may have no session yet, or be
71
+ * refused because tracking is suppressed), again whenever a handler
72
+ * refuses its signal while suppressed (noted on entry AND at the
73
+ * suppressed commit, since the handlers gate on the active provider and
74
+ * re-check suppression after an await), and again for all of them when
75
+ * an opt-out purges identity. It leaves the first time a handler commits
76
+ * its session unsuppressed, or when it is untracked. Retrying reads the
77
+ * provider's SYNCHRONOUS accounts only - no RPC - so a registered
78
+ * provider whose session never fires `accountsChanged` (a wallet that
79
+ * signals `connect` alone while `autocapture.connect` is off, which
80
+ * installs a chain-only observer) is still adopted on the next hit. "No
81
+ * RPC" holds for the pending provider itself; the handler's switch
82
+ * arbitration may still ask the ACTIVE provider for its accounts, as it
83
+ * does for any live signal.
84
+ *
85
+ * Replay goes through the accounts handler, which has live wallet-switch
86
+ * semantics: a different provider with a different address is a switch.
87
+ * So a pending provider is replayed only while no OTHER wallet is active
88
+ * and known - it waits, at no cost, until that wallet is gone - unless
89
+ * its own signal was refused (`latestRefused`): then the replay does
90
+ * exactly what the live signal would have done, switch included.
91
+ */
92
+ private pendingAdoptions;
93
+ /**
94
+ * The pending provider whose own live signal was refused while
95
+ * suppressed, LATEST only. The SDK follows one active wallet and the
96
+ * newest signal wins, so replaying only the last refused signal reaches
97
+ * the state the live signals would have reached, without the switches
98
+ * in between; an earlier refused provider stays pending, unprivileged.
99
+ */
100
+ private latestRefused?;
101
+ private dropRefusal;
102
+ /**
103
+ * An opt-out purges wallet identity. Every registered session is then
104
+ * unknown again (pending; no refusal is ADDED, so the opt-in replay of a
105
+ * merely connected wallet does not switch, while a refusal already
106
+ * standing from an excluded route keeps its meaning), and the
107
+ * opt-in retry must re-learn all of them: with the active wallet's
108
+ * address gone, the accounts handler cannot even tell a second wallet's
109
+ * accounts apart from the active one's, and would ignore them.
110
+ */
111
+ markRegisteredAdoptionsPending(): void;
112
+ /**
113
+ * Remember a registered provider's signal that suppression refuses.
114
+ *
115
+ * Replay privilege (`latestRefused`) goes only to a provider whose
116
+ * session the replay can actually read - synchronous accounts - so an
117
+ * accountless `connect` (a chain-only observation, or a session still
118
+ * pairing) cannot displace an earlier refusal that is adoptable.
119
+ */
120
+ private noteRefusalIfSuppressed;
121
+ /**
122
+ * Registered providers whose session ended and have not signalled a
123
+ * new one. Some providers keep stale synchronous `accounts` after a
124
+ * disconnect; replaying those would re-install a session that is over.
125
+ * Any later session signal (connect, accountsChanged) lifts this.
126
+ */
127
+ private awaitingNewSession;
128
+ /** A handler has learned this provider's session; nothing is pending. */
129
+ private settleAdoption;
130
+ /**
131
+ * A registered provider's session has ended. Its NEXT session is not
132
+ * yet learned, and may announce itself in a way no handler adopts (a
133
+ * `connect` alone while `autocapture.connect` is off installs a
134
+ * chain-only observer), so it is pending again for the page-hit retry.
135
+ */
136
+ private reopenAdoption;
137
+ /** One retry scan at a time; a call during a scan queues one more. */
138
+ private retryInFlight;
139
+ private retryRequested;
59
140
  /**
60
141
  * The connect this SDK has already reported for a provider.
61
142
  *
@@ -74,6 +155,11 @@ export declare class EvmEventTracker {
74
155
  constructor(wallet: WalletStateStore, registry: EvmProviderRegistry, deps: EvmEventTrackerDeps);
75
156
  /** Stop listening for wallet announcements. Called from SDK teardown. */
76
157
  cleanup(): void;
158
+ /** Set by `cleanup()`; every awaited continuation checks it. */
159
+ private disposed;
160
+ /** Bumped when a registered provider's session ends. See reopenAdoption. */
161
+ private sessionGenerations;
162
+ private sessionGeneration;
77
163
  /** Drop a provider's reported connect. Called when it stops being active. */
78
164
  forgetAnnouncedConnect(provider: EIP1193Provider): void;
79
165
  /**
@@ -109,14 +195,19 @@ export declare class EvmEventTracker {
109
195
  */
110
196
  adoptExternalProvider(detail: EIP6963ProviderDetail): boolean;
111
197
  /**
112
- * Re-run session adoption for every registered external provider.
198
+ * Finish what registration could not.
199
+ *
200
+ * A registered provider's session may not have existed at registration,
201
+ * or its adoption was refused because tracking was suppressed (opt-out,
202
+ * excluded route) - and an existing session may never emit another
203
+ * accountsChanged, so nothing else would ever retry. Called when
204
+ * suppression can have ended (opt-in, page navigation).
113
205
  *
114
- * Registration while tracking was suppressed (opt-out, excluded route)
115
- * reached the adoption path and was refused - and a provider whose
116
- * session already exists may never emit another accountsChanged, so
117
- * nothing would ever retry. Called when suppression can have ended
118
- * (opt-in, page navigation). Idempotent: an already-adopted wallet is
119
- * deduplicated by the same state and markers as any repeated signal.
206
+ * Adoption is retried ONLY for providers not yet adopted
207
+ * (`pendingAdoptions`), only from their synchronous accounts, and only
208
+ * once suppression has actually ended. The corrected-detect pass below
209
+ * runs for every registered provider: it is deduplicated per session by
210
+ * rdns, so repeating it is free.
120
211
  */
121
212
  retryExternalAdoptions(): void;
122
213
  private registerAccountsChangedListener;