@formo/analytics 1.35.0 → 1.35.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +134 -0
  2. package/dist/cjs/src/FormoAnalytics.js +647 -178
  3. package/dist/cjs/src/constants/base.d.ts +0 -1
  4. package/dist/cjs/src/constants/base.js +1 -3
  5. package/dist/cjs/src/event/EventFactory.js +1 -1
  6. package/dist/cjs/src/utils/mergeDeepRight.d.ts +22 -0
  7. package/dist/cjs/src/utils/mergeDeepRight.js +54 -0
  8. package/dist/cjs/src/version.d.ts +1 -1
  9. package/dist/cjs/src/version.js +1 -1
  10. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +193 -1
  11. package/dist/cjs/src/wagmi/WagmiEventHandler.js +1317 -77
  12. package/dist/cjs/src/wagmi/types.d.ts +6 -0
  13. package/dist/esm/src/FormoAnalytics.d.ts +134 -0
  14. package/dist/esm/src/FormoAnalytics.js +647 -178
  15. package/dist/esm/src/constants/base.d.ts +0 -1
  16. package/dist/esm/src/constants/base.js +0 -2
  17. package/dist/esm/src/event/EventFactory.js +1 -1
  18. package/dist/esm/src/utils/mergeDeepRight.d.ts +22 -0
  19. package/dist/esm/src/utils/mergeDeepRight.js +51 -0
  20. package/dist/esm/src/version.d.ts +1 -1
  21. package/dist/esm/src/version.js +1 -1
  22. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +193 -1
  23. package/dist/esm/src/wagmi/WagmiEventHandler.js +1315 -76
  24. package/dist/esm/src/wagmi/types.d.ts +6 -0
  25. package/dist/index.umd.min.js +1 -1
  26. package/package.json +2 -11
  27. package/dist/cjs/src/ramda/internal/_curry1.d.ts +0 -10
  28. package/dist/cjs/src/ramda/internal/_curry1.js +0 -26
  29. package/dist/cjs/src/ramda/internal/_curry2.d.ts +0 -10
  30. package/dist/cjs/src/ramda/internal/_curry2.js +0 -43
  31. package/dist/cjs/src/ramda/internal/_curry3.d.ts +0 -10
  32. package/dist/cjs/src/ramda/internal/_curry3.js +0 -74
  33. package/dist/cjs/src/ramda/internal/_has.d.ts +0 -2
  34. package/dist/cjs/src/ramda/internal/_has.js +0 -7
  35. package/dist/cjs/src/ramda/internal/_isObject.d.ts +0 -2
  36. package/dist/cjs/src/ramda/internal/_isObject.js +0 -7
  37. package/dist/cjs/src/ramda/internal/_isPlaceholder.d.ts +0 -2
  38. package/dist/cjs/src/ramda/internal/_isPlaceholder.js +0 -7
  39. package/dist/cjs/src/ramda/mergeDeepRight.d.ts +0 -3
  40. package/dist/cjs/src/ramda/mergeDeepRight.js +0 -14
  41. package/dist/cjs/src/ramda/mergeDeepWithKey.d.ts +0 -3
  42. package/dist/cjs/src/ramda/mergeDeepWithKey.js +0 -20
  43. package/dist/cjs/src/ramda/mergeWithKey.d.ts +0 -3
  44. package/dist/cjs/src/ramda/mergeWithKey.js +0 -26
  45. package/dist/esm/src/ramda/internal/_curry1.d.ts +0 -10
  46. package/dist/esm/src/ramda/internal/_curry1.js +0 -20
  47. package/dist/esm/src/ramda/internal/_curry2.d.ts +0 -10
  48. package/dist/esm/src/ramda/internal/_curry2.js +0 -37
  49. package/dist/esm/src/ramda/internal/_curry3.d.ts +0 -10
  50. package/dist/esm/src/ramda/internal/_curry3.js +0 -68
  51. package/dist/esm/src/ramda/internal/_has.d.ts +0 -2
  52. package/dist/esm/src/ramda/internal/_has.js +0 -4
  53. package/dist/esm/src/ramda/internal/_isObject.d.ts +0 -2
  54. package/dist/esm/src/ramda/internal/_isObject.js +0 -4
  55. package/dist/esm/src/ramda/internal/_isPlaceholder.d.ts +0 -2
  56. package/dist/esm/src/ramda/internal/_isPlaceholder.js +0 -4
  57. package/dist/esm/src/ramda/mergeDeepRight.d.ts +0 -3
  58. package/dist/esm/src/ramda/mergeDeepRight.js +0 -9
  59. package/dist/esm/src/ramda/mergeDeepWithKey.d.ts +0 -3
  60. package/dist/esm/src/ramda/mergeDeepWithKey.js +0 -15
  61. package/dist/esm/src/ramda/mergeWithKey.d.ts +0 -3
  62. package/dist/esm/src/ramda/mergeWithKey.js +0 -21
@@ -11,7 +11,6 @@ export declare const ACTIVE_WALLET_KEY = "active-wallet";
11
11
  export declare const ACTIVE_WALLET_TTL_MS: number;
12
12
  export declare const LOCAL_ANONYMOUS_ID_KEY = "anonymous-id";
13
13
  export declare const CONSENT_OPT_OUT_KEY = "opt-out-tracking";
14
- export declare const DEFAULT_PROVIDER_ICON = "data:image/svg+xml;base64,";
15
14
  export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
16
15
  export declare const DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
17
16
  export declare const BLOCKED_ADDRESSES: readonly ["0x0000000000000000000000000000000000000000", "0x000000000000000000000000000000000000dEaD"];
@@ -14,8 +14,6 @@ export var ACTIVE_WALLET_TTL_MS = 24 * 60 * 60 * 1000;
14
14
  export var LOCAL_ANONYMOUS_ID_KEY = "anonymous-id";
15
15
  // Consent management keys
16
16
  export var CONSENT_OPT_OUT_KEY = "opt-out-tracking";
17
- // Default provider icon (empty data URL)
18
- export var DEFAULT_PROVIDER_ICON = 'data:image/svg+xml;base64,';
19
17
  // Blocked addresses that should not emit events
20
18
  export var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
21
19
  export var DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
@@ -60,7 +60,7 @@ import { validateAddress } from "../utils/address";
60
60
  import { getCurrentTimeFormatted } from "../utils/timestamp";
61
61
  import { isUndefined } from "../validators";
62
62
  import { logger } from "../logger";
63
- import mergeDeepRight from "../ramda/mergeDeepRight";
63
+ import mergeDeepRight from "../utils/mergeDeepRight";
64
64
  import { session } from "../storage";
65
65
  import { version } from "../version";
66
66
  import { CHANNEL, CLICK_ID_PARAMS, DEFAULT_EXCLUDED_QUERY_PARAMS, DEFAULT_REFERRAL_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, VERSION, } from "./constants";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Deep merge two plain objects, with the right-hand side winning.
3
+ *
4
+ * Replaces a vendored copy of ramda's `mergeDeepRight` (9 files, ~200 lines
5
+ * of currying and placeholder machinery) that existed to serve exactly two
6
+ * call sites, each passing two plain objects. None of the currying was
7
+ * reachable.
8
+ *
9
+ * Behaviour is deliberately identical to the implementation it replaces:
10
+ *
11
+ * - Only PLAIN objects recurse. `Object.prototype.toString` is the test, so
12
+ * arrays, dates, class instances, null and primitives are all replaced
13
+ * wholesale by the right-hand value rather than merged into.
14
+ * - Own enumerable keys only, via `hasOwnProperty` - inherited keys are not
15
+ * copied, and a key literally named `hasOwnProperty` cannot break it.
16
+ * - A key present on the left and absent on the right keeps the left value.
17
+ * - Neither input is mutated; a new object is returned at every level.
18
+ * - Nullish inputs are treated as empty objects.
19
+ */
20
+ export declare function mergeDeepRight<L, R>(left: L, right: R): L & R;
21
+ export default mergeDeepRight;
22
+ //# sourceMappingURL=mergeDeepRight.d.ts.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Deep merge two plain objects, with the right-hand side winning.
3
+ *
4
+ * Replaces a vendored copy of ramda's `mergeDeepRight` (9 files, ~200 lines
5
+ * of currying and placeholder machinery) that existed to serve exactly two
6
+ * call sites, each passing two plain objects. None of the currying was
7
+ * reachable.
8
+ *
9
+ * Behaviour is deliberately identical to the implementation it replaces:
10
+ *
11
+ * - Only PLAIN objects recurse. `Object.prototype.toString` is the test, so
12
+ * arrays, dates, class instances, null and primitives are all replaced
13
+ * wholesale by the right-hand value rather than merged into.
14
+ * - Own enumerable keys only, via `hasOwnProperty` - inherited keys are not
15
+ * copied, and a key literally named `hasOwnProperty` cannot break it.
16
+ * - A key present on the left and absent on the right keeps the left value.
17
+ * - Neither input is mutated; a new object is returned at every level.
18
+ * - Nullish inputs are treated as empty objects.
19
+ */
20
+ export function mergeDeepRight(left, right) {
21
+ var l = (left !== null && left !== void 0 ? left : {});
22
+ var r = (right !== null && right !== void 0 ? right : {});
23
+ var result = {};
24
+ for (var k in l) {
25
+ if (!Object.prototype.hasOwnProperty.call(l, k))
26
+ continue;
27
+ result[k] = Object.prototype.hasOwnProperty.call(r, k)
28
+ ? mergeValues(l[k], r[k])
29
+ : l[k];
30
+ }
31
+ for (var k in r) {
32
+ if (!Object.prototype.hasOwnProperty.call(r, k))
33
+ continue;
34
+ if (Object.prototype.hasOwnProperty.call(result, k))
35
+ continue;
36
+ result[k] = r[k];
37
+ }
38
+ return result;
39
+ }
40
+ /** Recurse only when BOTH sides are plain objects; otherwise the right wins. */
41
+ function mergeValues(lVal, rVal) {
42
+ if (isPlainObject(lVal) && isPlainObject(rVal)) {
43
+ return mergeDeepRight(lVal, rVal);
44
+ }
45
+ return rVal;
46
+ }
47
+ function isPlainObject(x) {
48
+ return Object.prototype.toString.call(x) === "[object Object]";
49
+ }
50
+ export default mergeDeepRight;
51
+ //# sourceMappingURL=mergeDeepRight.js.map
@@ -1,2 +1,2 @@
1
- export declare const version = "1.35.0";
1
+ export declare const version = "1.35.2";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // This file is auto-generated by scripts/update-version.js during npm version
2
2
  // Do not edit manually - it will be overwritten
3
- export var version = '1.35.0';
3
+ export var version = '1.35.2';
4
4
  //# sourceMappingURL=version.js.map
@@ -7,12 +7,72 @@
7
7
  */
8
8
  import { FormoAnalytics } from "../FormoAnalytics";
9
9
  import { WagmiConfig, QueryClient } from "./types";
10
+ /**
11
+ * How long the markers stay trustworthy after the last handler goes away.
12
+ *
13
+ * A marker only means "already announced" while the SDK is actually watching
14
+ * wagmi. With no handler mounted nothing observes a disconnect, so a wallet
15
+ * that disconnects and reconnects in that window would otherwise look
16
+ * unchanged and its genuine connect would be swallowed for the rest of the
17
+ * page load.
18
+ *
19
+ * Rebuilding the SDK tears the old handler down and builds a new one, and
20
+ * `FormoAnalytics.init()` is async, so the gap is real but short. This grace
21
+ * period spans a rebuild and little else: markers survive it, and a longer
22
+ * unmount discards them so the next connect is treated as new.
23
+ */
24
+ export declare const MARKER_GRACE_MS = 3000;
25
+ /** Test hook. Real page loads reset this naturally. */
26
+ export declare function __resetSeededWallet(): void;
10
27
  export declare class WagmiEventHandler {
11
28
  private formo;
12
29
  private wagmiConfig;
13
30
  private queryClient?;
14
31
  private unsubscribers;
15
32
  private trackingState;
33
+ /**
34
+ * Set by `cleanup()`. Every deferred emission checks it, so a handler that
35
+ * has been torn down cannot push an event into a queue the replacement SDK
36
+ * instance no longer owns.
37
+ */
38
+ private disposed;
39
+ /**
40
+ * Drop markers for wallets wagmi no longer holds, and report whether any
41
+ * announced wallet is still connected.
42
+ *
43
+ * Called when a handler mounts. Cancelling the expiry timer on the strength
44
+ * of "some connection exists" let a marker for a wallet that had since
45
+ * disconnected survive indefinitely, suppressing its genuine reconnect.
46
+ */
47
+ private pruneMarkersForLostWallets;
48
+ /** Identifies this handler's destination for emit-ownership. */
49
+ private ownerKey?;
50
+ /**
51
+ * Whether this handler is the one allowed to emit for its destination.
52
+ *
53
+ * A handler that lost the race still tracks state, so it is ready to take
54
+ * over the moment the owner goes away; it just does not emit.
55
+ */
56
+ private get isEmittingOwner();
57
+ /** Reentrancy guard for `reconcileWithLiveState()`. */
58
+ private reconciling;
59
+ /**
60
+ * Ticket for each active-connection transition. Emissions are awaited, and
61
+ * wagmi can move again in that window; a continuation whose ticket is no
62
+ * longer current must not write stale state over the newer one.
63
+ */
64
+ private transitionGeneration;
65
+ /**
66
+ * A chain reported while wagmi was not `connected`, so the chain callback
67
+ * had to drop it. Replayed by `reconcileWithLiveState()`.
68
+ */
69
+ private pendingChainId?;
70
+ /**
71
+ * A `disconnected` status that arrived while the lock was held. Final-state
72
+ * reconciliation cannot see a disconnect/reconnect cycle that completed
73
+ * inside that window, so this records that one happened.
74
+ */
75
+ private missedDisconnect;
16
76
  /**
17
77
  * Track processed mutation states to prevent duplicate event emissions
18
78
  * Key format: `${mutationId}:${status}`
@@ -27,16 +87,130 @@ export declare class WagmiEventHandler {
27
87
  * Store transaction details from BROADCASTED events for use in CONFIRMED/REVERTED
28
88
  * Key: transactionHash, Value: transaction details including the original sender address
29
89
  */
30
- private pendingTransactions;
90
+ /**
91
+ * Shared per destination, not per handler.
92
+ *
93
+ * A broadcast observed by handler A, followed by a replacement B taking
94
+ * over before the receipt arrives, used to lose the confirmation entirely:
95
+ * A no longer emits because it is not the owner, and B had no record of the
96
+ * broadcast to match the receipt against.
97
+ */
98
+ private get pendingTransactions();
31
99
  constructor(formoAnalytics: FormoAnalytics, wagmiConfig: WagmiConfig, queryClient?: QueryClient);
32
100
  /**
33
101
  * Set up listeners for wallet connection, disconnection, and chain changes
34
102
  */
35
103
  private setupConnectionListeners;
104
+ /**
105
+ * Adopt a connection that already existed when this handler was created.
106
+ *
107
+ * `config.subscribe` reports *changes* only, so a wallet restored by wagmi's
108
+ * mount-time `reconnect()` is invisible to a handler built afterwards. That
109
+ * is the normal case whenever the host app loads the SDK lazily (dynamic
110
+ * import, `requestIdleCallback`) and can also happen on a plain mount, since
111
+ * `FormoAnalytics.init()` and wagmi's reconnect are both async and race.
112
+ *
113
+ * Without this seed the whole session is lost: no connect event, and
114
+ * `lastAddress` stays undefined so the signature and transaction handlers
115
+ * drop their events too, and the eventual disconnect carries neither address
116
+ * nor chain id.
117
+ *
118
+ * `subscribe(..., { fireImmediately: true })` is not a substitute. It reports
119
+ * the current value with no distinct previous value, so the connect branch's
120
+ * `prevStatus !== "connected"` test never passes, and wagmi v3 does not
121
+ * honour the option at all.
122
+ *
123
+ * Deliberately synchronous, and deliberately does NOT take the
124
+ * `isProcessing` lock. Everything that mutates tracking state runs before
125
+ * any await, so no status change can interleave with it. Holding the lock
126
+ * across the `connect()` emission instead would make `handleStatusChange()`
127
+ * drop - not defer - a disconnect or wallet switch that lands in that
128
+ * window, which is exactly when a lazily loaded SDK is racing app activity.
129
+ *
130
+ * A genuine `connected` transition cannot double-emit alongside this seed.
131
+ * `config.subscribe` only fires on change, and the connect branch treats a
132
+ * re-entry for an already-tracked address as a no-op (or a chain change).
133
+ */
134
+ private seedFromCurrentState;
135
+ /**
136
+ * Re-assert the tracked wallet into central state.
137
+ *
138
+ * Needed when an awaited emission clears the central namespace after a newer
139
+ * transition has already adopted a different wallet: the two then disagree,
140
+ * and `trackEvent()` reads the central one.
141
+ */
142
+ private restoreCentralStateFromTracking;
143
+ /**
144
+ * Drop the wallet this handler is attributing events to.
145
+ *
146
+ * Used whenever central state refuses to hold the wallet - an excluded path
147
+ * or chain, or an opt-out. Keeping the address privately would let the
148
+ * mutation handlers label signatures and transactions with an identity the
149
+ * SDK has deliberately declined to know.
150
+ */
151
+ private releaseTrackedWallet;
152
+ /**
153
+ * Adopt the live connection if one exists and nothing is tracked yet.
154
+ *
155
+ * `seedFromCurrentState()` runs once at construction and can legitimately
156
+ * decline: wagmi may still be filling `connections`, the chain may not have
157
+ * arrived, or tracking may be suppressed because the app happens to be on an
158
+ * excluded path. `config.subscribe` only reports *changes*, so an unchanged
159
+ * connection would then stay invisible for the rest of the page load and
160
+ * every later signature and transaction would be dropped.
161
+ *
162
+ * Idempotent. The page-load marker keeps it from re-emitting a connect for a
163
+ * wallet already announced, so this is safe to call from any listener.
164
+ */
165
+ retryAdoption(): void;
166
+ /** Whether the wallet this handler tracks is the one wagmi currently has. */
167
+ private tracksLiveWallet;
168
+ /**
169
+ * Put the tracked wallet back into central state without emitting anything.
170
+ *
171
+ * Used when the two have diverged for a reason that is not a wallet change:
172
+ * an opt-out `reset()`, or a stale disconnect completing after a newer
173
+ * transition adopted a different wallet.
174
+ */
175
+ private resyncCentralState;
176
+ /** Whether the wallet currently tracked has already had a connect emitted. */
177
+ private isCurrentWalletAnnounced;
36
178
  /**
37
179
  * Handle status changes (connect/disconnect)
38
180
  */
39
181
  private handleStatusChange;
182
+ /**
183
+ * Re-run the status logic when the handler's view diverges from wagmi.
184
+ *
185
+ * `handleStatusChange()` drops - rather than defers - anything that arrives
186
+ * while it holds `isProcessing`, and it holds that lock across an awaited
187
+ * `connect()` / `disconnect()` emission. A wallet that reconnects inside
188
+ * that window would otherwise leave the handler believing it is
189
+ * disconnected for the rest of the page load, silently dropping every later
190
+ * signature and transaction. The mirror case leaves a disconnected wallet
191
+ * marked connected.
192
+ *
193
+ * Converges: each pass either adopts the live wallet or releases the tracked
194
+ * one, so the next pass finds the two in agreement and returns.
195
+ */
196
+ private reconcileWithLiveState;
197
+ /**
198
+ * Handle a switch to a different account on an already-connected wallet.
199
+ *
200
+ * Only fires for a genuine in-place switch. A fresh connect and a disconnect
201
+ * both move `state.status`, and that listener is registered first, so it has
202
+ * already recorded the new address synchronously by the time this runs. The
203
+ * `lastAddress` check below is what makes the two paths mutually exclusive.
204
+ */
205
+ private handleActiveAddressChange;
206
+ /**
207
+ * Record and emit a chain move for the wallet already being tracked.
208
+ *
209
+ * Shared by `handleChainChange()` and the connector-fallback path, which
210
+ * has to apply the chain itself: the chain callback defers while the new
211
+ * connection is not adopted yet, so nothing else would.
212
+ */
213
+ private applyChainForTrackedWallet;
40
214
  /**
41
215
  * Handle chain ID changes
42
216
  */
@@ -77,10 +251,28 @@ export declare class WagmiEventHandler {
77
251
  * for compatibility with different Wagmi wrappers (RainbowKit, etc.)
78
252
  */
79
253
  private getState;
254
+ /**
255
+ * Chain of the connection that is currently active.
256
+ *
257
+ * Authoritative, because `state.chainId` is a single global value. With
258
+ * `syncConnectedChain: false` it stays on the chain the APP selected while
259
+ * the connection reports the chain the WALLET is actually on, so seeding
260
+ * from the global could label a wallet on an excluded chain as an allowed
261
+ * one and send events the exclusion forbids.
262
+ */
263
+ private getActiveConnectionChainId;
80
264
  /**
81
265
  * Get the currently connected address from Wagmi state
82
266
  */
83
267
  private getConnectedAddress;
268
+ /**
269
+ * Whether any live connection still holds this address.
270
+ *
271
+ * Distinguishes "the user switched away from this account" (gone from every
272
+ * connection) from "this account merely stopped being the current one"
273
+ * (still connected through its own connector).
274
+ */
275
+ private isAddressConnected;
84
276
  /**
85
277
  * Get the connector name from Wagmi state
86
278
  */