@formo/analytics 1.29.0 → 1.30.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 (48) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +52 -3
  2. package/dist/cjs/src/FormoAnalytics.js +262 -51
  3. package/dist/cjs/src/constants/base.d.ts +9 -0
  4. package/dist/cjs/src/constants/base.js +10 -1
  5. package/dist/cjs/src/constants/config.d.ts +3 -58
  6. package/dist/cjs/src/constants/config.js +3 -73
  7. package/dist/cjs/src/event/EventFactory.d.ts +1 -1
  8. package/dist/cjs/src/event/EventFactory.js +12 -6
  9. package/dist/cjs/src/event/EventManager.d.ts +2 -0
  10. package/dist/cjs/src/event/EventManager.js +4 -0
  11. package/dist/cjs/src/event/type.d.ts +1 -0
  12. package/dist/cjs/src/event/utils.js +1 -1
  13. package/dist/cjs/src/queue/EventQueue.d.ts +8 -0
  14. package/dist/cjs/src/queue/EventQueue.js +52 -6
  15. package/dist/cjs/src/queue/type.d.ts +1 -0
  16. package/dist/cjs/src/session/index.js +16 -8
  17. package/dist/cjs/src/storage/cookiePolicy.d.ts +16 -0
  18. package/dist/cjs/src/storage/cookiePolicy.js +19 -0
  19. package/dist/cjs/src/types/base.d.ts +0 -1
  20. package/dist/cjs/src/types/events.d.ts +0 -1
  21. package/dist/cjs/src/version.d.ts +1 -1
  22. package/dist/cjs/src/version.js +1 -1
  23. package/dist/cjs/src/wagmi/WagmiEventHandler.js +38 -15
  24. package/dist/esm/src/FormoAnalytics.d.ts +52 -3
  25. package/dist/esm/src/FormoAnalytics.js +265 -54
  26. package/dist/esm/src/constants/base.d.ts +9 -0
  27. package/dist/esm/src/constants/base.js +9 -0
  28. package/dist/esm/src/constants/config.d.ts +3 -58
  29. package/dist/esm/src/constants/config.js +3 -73
  30. package/dist/esm/src/event/EventFactory.d.ts +1 -1
  31. package/dist/esm/src/event/EventFactory.js +12 -6
  32. package/dist/esm/src/event/EventManager.d.ts +2 -0
  33. package/dist/esm/src/event/EventManager.js +4 -0
  34. package/dist/esm/src/event/type.d.ts +1 -0
  35. package/dist/esm/src/event/utils.js +2 -2
  36. package/dist/esm/src/queue/EventQueue.d.ts +8 -0
  37. package/dist/esm/src/queue/EventQueue.js +52 -6
  38. package/dist/esm/src/queue/type.d.ts +1 -0
  39. package/dist/esm/src/session/index.js +16 -8
  40. package/dist/esm/src/storage/cookiePolicy.d.ts +16 -0
  41. package/dist/esm/src/storage/cookiePolicy.js +18 -0
  42. package/dist/esm/src/types/base.d.ts +0 -1
  43. package/dist/esm/src/types/events.d.ts +0 -1
  44. package/dist/esm/src/version.d.ts +1 -1
  45. package/dist/esm/src/version.js +1 -1
  46. package/dist/esm/src/wagmi/WagmiEventHandler.js +38 -15
  47. package/dist/index.umd.min.js +1 -1
  48. package/package.json +13 -23
@@ -76,7 +76,6 @@ export interface SignatureAPIEvent {
76
76
  chainId: ChainID;
77
77
  address: Address;
78
78
  message: string;
79
- signatureHash?: string;
80
79
  }
81
80
  export interface ConnectAPIEvent {
82
81
  type: "connect";
@@ -1,2 +1,2 @@
1
- export declare const version = "1.29.0";
1
+ export declare const version = "1.30.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
4
  // This file is auto-generated by scripts/update-version.js during npm version
5
5
  // Do not edit manually - it will be overwritten
6
- exports.version = '1.29.0';
6
+ exports.version = '1.30.0';
7
7
  //# sourceMappingURL=version.js.map
@@ -173,6 +173,11 @@ var WagmiEventHandler = /** @class */ (function () {
173
173
  chainId: chainId,
174
174
  });
175
175
  if (!(status === "disconnected" && prevStatus === "connected")) return [3 /*break*/, 4];
176
+ // Clear central chain state regardless of autocapture so a later
177
+ // event can't carry a stale excluded/!excluded chainId.
178
+ this.formo.syncWalletState({
179
+ chainId: this.trackingState.lastChainId,
180
+ });
176
181
  if (!this.formo.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 3];
177
182
  return [4 /*yield*/, this.formo.disconnect({
178
183
  chainId: this.trackingState.lastChainId,
@@ -190,6 +195,9 @@ var WagmiEventHandler = /** @class */ (function () {
190
195
  if (!(address && chainId !== undefined)) return [3 /*break*/, 6];
191
196
  this.trackingState.lastAddress = address;
192
197
  this.trackingState.lastChainId = chainId;
198
+ // Sync central state unconditionally so tracking.excludeChains
199
+ // is enforced even when connect autocapture is disabled.
200
+ this.formo.syncWalletState({ chainId: chainId, address: address });
193
201
  if (!this.formo.isAutocaptureEnabled("connect")) return [3 /*break*/, 6];
194
202
  connectorName = this.getConnectorName(state);
195
203
  return [4 /*yield*/, this.formo.connect({ chainId: chainId, address: address }, __assign({}, (connectorName && { providerName: connectorName })))];
@@ -238,6 +246,9 @@ var WagmiEventHandler = /** @class */ (function () {
238
246
  address: address,
239
247
  });
240
248
  this.trackingState.lastChainId = chainId;
249
+ // Sync central state unconditionally so a chain switch to an
250
+ // excluded chain is honored even when chain autocapture is disabled.
251
+ this.formo.syncWalletState({ chainId: chainId, address: address });
241
252
  if (!this.formo.isAutocaptureEnabled("chain")) return [3 /*break*/, 4];
242
253
  _a.label = 1;
243
254
  case 1:
@@ -359,14 +370,21 @@ var WagmiEventHandler = /** @class */ (function () {
359
370
  logger_1.logger.warn("WagmiEventHandler: Transaction receipt query but no hash found");
360
371
  return;
361
372
  }
362
- // Retrieve stored transaction details from BROADCASTED event
363
- // Normalize hash to lowercase for consistent lookup
373
+ // Retrieve stored transaction details from the BROADCASTED event.
374
+ // Normalize hash to lowercase for consistent lookup.
364
375
  var normalizedHash = transactionHash.toLowerCase();
365
376
  var pendingTx = this.pendingTransactions.get(normalizedHash);
366
- // Use the original sender address from BROADCASTED event if available,
367
- // otherwise fall back to current connected address.
368
- // This handles wallet switches between broadcast and confirmation.
369
- var address = (pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.address) || this.trackingState.lastAddress;
377
+ // Only emit receipt-derived events for a hash we actually observed
378
+ // being broadcast through this handler. The QueryClient is supplied
379
+ // by the host app and its cache can be written by app code or other
380
+ // deps, so without this gate a forged waitForTransactionReceipt
381
+ // entry could fabricate a confirmed/reverted transaction for an
382
+ // arbitrary hash. No pendingTx → not our broadcast → ignore.
383
+ if (!pendingTx) {
384
+ logger_1.logger.debug("WagmiEventHandler: Receipt for unobserved tx hash; ignoring", { transactionHash: transactionHash });
385
+ return;
386
+ }
387
+ var address = pendingTx.address;
370
388
  if (!address) {
371
389
  logger_1.logger.warn("WagmiEventHandler: Transaction receipt query but no address available");
372
390
  return;
@@ -374,9 +392,13 @@ var WagmiEventHandler = /** @class */ (function () {
374
392
  try {
375
393
  // Extract receipt data
376
394
  var receipt = state.data;
377
- // Determine transaction status from receipt
378
- // receipt.status is 'success' or 'reverted' in viem
379
- var txStatus = (receipt === null || receipt === void 0 ? void 0 : receipt.status) === "reverted"
395
+ // Only act on an explicit on-chain outcome. A missing/unknown
396
+ // status must NOT be treated as a confirmation.
397
+ if ((receipt === null || receipt === void 0 ? void 0 : receipt.status) !== "success" && (receipt === null || receipt === void 0 ? void 0 : receipt.status) !== "reverted") {
398
+ logger_1.logger.debug("WagmiEventHandler: Receipt without explicit success/reverted status; ignoring", { transactionHash: transactionHash, status: receipt === null || receipt === void 0 ? void 0 : receipt.status });
399
+ return;
400
+ }
401
+ var txStatus = receipt.status === "reverted"
380
402
  ? events_1.TransactionStatus.REVERTED
381
403
  : events_1.TransactionStatus.CONFIRMED;
382
404
  logger_1.logger.info("WagmiEventHandler: Tracking transaction confirmation", {
@@ -455,15 +477,13 @@ var WagmiEventHandler = /** @class */ (function () {
455
477
  return;
456
478
  }
457
479
  try {
458
- // Map Wagmi mutation status to Formo signature status
480
+ // Map Wagmi mutation status to Formo signature status.
459
481
  var status_1;
460
- var signatureHash = void 0;
461
482
  if (state.status === "pending") {
462
483
  status_1 = events_1.SignatureStatus.REQUESTED;
463
484
  }
464
485
  else if (state.status === "success") {
465
486
  status_1 = events_1.SignatureStatus.CONFIRMED;
466
- signatureHash = state.data;
467
487
  }
468
488
  else if (state.status === "error") {
469
489
  status_1 = events_1.SignatureStatus.REJECTED;
@@ -471,13 +491,11 @@ var WagmiEventHandler = /** @class */ (function () {
471
491
  else {
472
492
  return; // Ignore idle state
473
493
  }
474
- // Extract message from variables
475
494
  var message = void 0;
476
495
  if (mutationType === "signMessage") {
477
496
  message = variables.message || "";
478
497
  }
479
498
  else {
480
- // For signTypedData, stringify the typed data
481
499
  message = JSON.stringify(variables.message || variables.types || {});
482
500
  }
483
501
  logger_1.logger.info("WagmiEventHandler: Tracking signature event", {
@@ -486,7 +504,12 @@ var WagmiEventHandler = /** @class */ (function () {
486
504
  address: address,
487
505
  chainId: chainId,
488
506
  });
489
- this.formo.signature(__assign({ status: status_1, chainId: chainId, address: address, message: message }, (signatureHash && { signatureHash: signatureHash })));
507
+ this.formo.signature({
508
+ status: status_1,
509
+ chainId: chainId,
510
+ address: address,
511
+ message: message,
512
+ });
490
513
  }
491
514
  catch (error) {
492
515
  logger_1.logger.error("WagmiEventHandler: Error handling signature mutation:", error);
@@ -54,6 +54,10 @@ export declare class FormoAnalytics implements IFormoAnalytics {
54
54
  private crossSubdomainCookies;
55
55
  /** In-memory URL used to deduplicate SPA pageview events. */
56
56
  private _currentUrl;
57
+ /** Page-hit hooks installed in trackPageHits() so cleanup() can undo them. */
58
+ private _onPopStateListener?;
59
+ private _onLocationChangeListener?;
60
+ private _pageHooksDisposed;
57
61
  config: Config;
58
62
  currentChainId?: ChainID;
59
63
  currentAddress?: Address;
@@ -132,18 +136,16 @@ export declare class FormoAnalytics implements IFormoAnalytics {
132
136
  * @param {ChainID} params.chainId
133
137
  * @param {Address} params.address
134
138
  * @param {string} params.message
135
- * @param {string} params.signatureHash - only provided if status is confirmed
136
139
  * @param {IFormoEventProperties} properties
137
140
  * @param {IFormoEventContext} context
138
141
  * @param {(...args: unknown[]) => void} callback
139
142
  * @returns {Promise<void>}
140
143
  */
141
- signature({ status, chainId, address, message, signatureHash, }: {
144
+ signature({ status, chainId, address, message, }: {
142
145
  status: SignatureStatus;
143
146
  chainId?: ChainID;
144
147
  address: Address;
145
148
  message: string;
146
- signatureHash?: string;
147
149
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
148
150
  /**
149
151
  * Emits a transaction event.
@@ -272,6 +274,12 @@ export declare class FormoAnalytics implements IFormoAnalytics {
272
274
  private registerRequestListeners;
273
275
  private onLocationChange;
274
276
  private trackPageHits;
277
+ /**
278
+ * Wrap history.pushState / replaceState exactly once per `history` object,
279
+ * regardless of how many SDK instances are constructed. Uses a Symbol
280
+ * marker so we recognize our own wrapper across module reloads in HMR.
281
+ */
282
+ private static installHistoryHooksOnce;
275
283
  private trackPageHit;
276
284
  private trackEvent;
277
285
  /**
@@ -312,6 +320,15 @@ export declare class FormoAnalytics implements IFormoAnalytics {
312
320
  private getCurrentChainId;
313
321
  private buildSignatureEventPayload;
314
322
  private buildTransactionEventPayload;
323
+ /**
324
+ * Persist an EVM address discovered through autocapture (signature / transaction)
325
+ * as the current EVM address when none is currently set. This lets subsequent
326
+ * track()/page() calls carry the address even when the underlying wallet never
327
+ * fires an EIP-1193 `accountsChanged` event (embedded wallets, smart accounts,
328
+ * social-login wrappers). If `accountsChanged` later fires it overwrites this
329
+ * value in the normal way; existing connections are never clobbered.
330
+ */
331
+ private backfillActiveWallet;
315
332
  /**
316
333
  * Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
317
334
  */
@@ -362,11 +379,43 @@ export declare class FormoAnalytics implements IFormoAnalytics {
362
379
  * Clear per-chain state for a given namespace (or chainId) and sync derived state.
363
380
  */
364
381
  private clearChainState;
382
+ /**
383
+ * Sync validated wallet/chain state into the SDK's central state
384
+ * WITHOUT emitting an event.
385
+ *
386
+ * Integrations (e.g. the wagmi handler) must call this on every
387
+ * connect / chain-change / disconnect — even when the corresponding
388
+ * autocapture event is disabled. Otherwise `currentChainId` stays
389
+ * stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
390
+ * check (which keys off `currentChainId`, not the event payload) can
391
+ * be bypassed, letting wallet activity on an excluded chain still be
392
+ * collected.
393
+ *
394
+ * - valid `address` present → record per-chain + derived state
395
+ * - `address` absent → clear chain state (disconnect)
396
+ */
397
+ syncWalletState(params: {
398
+ chainId?: ChainID;
399
+ address?: Address;
400
+ }): void;
365
401
  /**
366
402
  * Synchronize currentAddress/currentChainId from the active namespace.
367
403
  * Last-connected-chain-wins: _activeNamespace takes precedence.
368
404
  */
369
405
  private syncDerivedState;
406
+ /**
407
+ * Persist (or clear) the current wallet snapshot in a cookie so that the
408
+ * SDK can repopulate `currentAddress`/`currentChainId` at init on the next
409
+ * page load — closing the gap between page-show and wagmi/EIP-1193
410
+ * reconnection during which track()/page() events would otherwise ship
411
+ * with an empty address.
412
+ */
413
+ private persistActiveWallet;
414
+ /**
415
+ * Seed `currentAddress`/`currentChainId` from the persisted snapshot, if
416
+ * any. Called once during construction before the first page hit fires.
417
+ */
418
+ private loadActiveWallet;
370
419
  /**
371
420
  * Helper method to clear the active provider state
372
421
  * Centralizes provider clearing logic for consistency