@formo/analytics 1.29.1 → 1.30.1

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 (50) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +20 -3
  2. package/dist/cjs/src/FormoAnalytics.js +102 -9
  3. package/dist/cjs/src/FormoAnalyticsProvider.d.ts +8 -2
  4. package/dist/cjs/src/core.d.ts +10 -0
  5. package/dist/cjs/src/core.js +36 -0
  6. package/dist/cjs/src/event/EventFactory.d.ts +1 -1
  7. package/dist/cjs/src/event/EventFactory.js +3 -3
  8. package/dist/cjs/src/event/EventManager.d.ts +2 -0
  9. package/dist/cjs/src/event/EventManager.js +4 -0
  10. package/dist/cjs/src/event/type.d.ts +1 -0
  11. package/dist/cjs/src/event/utils.js +1 -1
  12. package/dist/cjs/src/index.d.ts +1 -8
  13. package/dist/cjs/src/index.js +1 -12
  14. package/dist/cjs/src/queue/EventQueue.d.ts +8 -0
  15. package/dist/cjs/src/queue/EventQueue.js +52 -6
  16. package/dist/cjs/src/queue/type.d.ts +1 -0
  17. package/dist/cjs/src/session/index.js +16 -8
  18. package/dist/cjs/src/storage/cookiePolicy.d.ts +16 -0
  19. package/dist/cjs/src/storage/cookiePolicy.js +19 -0
  20. package/dist/cjs/src/types/base.d.ts +0 -8
  21. package/dist/cjs/src/types/events.d.ts +0 -1
  22. package/dist/cjs/src/version.d.ts +1 -1
  23. package/dist/cjs/src/version.js +1 -1
  24. package/dist/cjs/src/wagmi/WagmiEventHandler.js +38 -15
  25. package/dist/esm/src/FormoAnalytics.d.ts +20 -3
  26. package/dist/esm/src/FormoAnalytics.js +105 -12
  27. package/dist/esm/src/FormoAnalyticsProvider.d.ts +8 -2
  28. package/dist/esm/src/core.d.ts +10 -0
  29. package/dist/esm/src/core.js +13 -0
  30. package/dist/esm/src/event/EventFactory.d.ts +1 -1
  31. package/dist/esm/src/event/EventFactory.js +3 -3
  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/index.d.ts +1 -8
  37. package/dist/esm/src/index.js +1 -6
  38. package/dist/esm/src/queue/EventQueue.d.ts +8 -0
  39. package/dist/esm/src/queue/EventQueue.js +52 -6
  40. package/dist/esm/src/queue/type.d.ts +1 -0
  41. package/dist/esm/src/session/index.js +16 -8
  42. package/dist/esm/src/storage/cookiePolicy.d.ts +16 -0
  43. package/dist/esm/src/storage/cookiePolicy.js +18 -0
  44. package/dist/esm/src/types/base.d.ts +0 -8
  45. package/dist/esm/src/types/events.d.ts +0 -1
  46. package/dist/esm/src/version.d.ts +1 -1
  47. package/dist/esm/src/version.js +1 -1
  48. package/dist/esm/src/wagmi/WagmiEventHandler.js +38 -15
  49. package/dist/index.umd.min.js +1 -1
  50. package/package.json +18 -18
@@ -18,6 +18,8 @@ declare class EventManager implements IEventManager {
18
18
  * @param event Incoming event data
19
19
  */
20
20
  addEvent(event: APIEvent, address?: Address, userId?: string): Promise<void>;
21
+ /** Drop any buffered events (consent withdrawal / teardown). */
22
+ clear(): void;
21
23
  }
22
24
  export { EventManager };
23
25
  //# sourceMappingURL=EventManager.d.ts.map
@@ -93,6 +93,10 @@ var EventManager = /** @class */ (function () {
93
93
  });
94
94
  });
95
95
  };
96
+ /** Drop any buffered events (consent withdrawal / teardown). */
97
+ EventManager.prototype.clear = function () {
98
+ this.eventQueue.clear();
99
+ };
96
100
  return EventManager;
97
101
  }());
98
102
  export { EventManager };
@@ -1,6 +1,7 @@
1
1
  import { Address, APIEvent, IFormoEvent } from "../types";
2
2
  export interface IEventManager {
3
3
  addEvent(event: APIEvent, address?: Address, userId?: string): Promise<void>;
4
+ clear(): void;
4
5
  }
5
6
  export interface IEventFactory {
6
7
  create(event: APIEvent, address?: Address, userId?: string): Promise<IFormoEvent>;
@@ -11,7 +11,7 @@ var __assign = (this && this.__assign) || function () {
11
11
  };
12
12
  import { generateNativeUUID } from "../utils";
13
13
  import { cookie } from "../storage";
14
- import { getIdentityCookieDomain } from "../storage/cookiePolicy";
14
+ import { getIdentityCookieDomain, getIdentityCookieSecurity, } from "../storage/cookiePolicy";
15
15
  var generateAnonymousId = function (key, crossSubdomainCookies) {
16
16
  var storedAnonymousId = cookie().get(key);
17
17
  var anonymousId = (storedAnonymousId && typeof storedAnonymousId === "string"
@@ -23,7 +23,7 @@ var generateAnonymousId = function (key, crossSubdomainCookies) {
23
23
  // domain. Note: host-only cookies on other hosts (e.g. a cookie set on
24
24
  // example.com is not visible from app.example.com) cannot be migrated
25
25
  // until the user revisits that host.
26
- cookie().set(key, anonymousId, __assign({ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString(), path: "/" }, (domain ? { domain: domain } : {})));
26
+ cookie().set(key, anonymousId, __assign(__assign({ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString(), path: "/" }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
27
27
  return anonymousId;
28
28
  };
29
29
  export { generateAnonymousId };
@@ -1,10 +1,3 @@
1
+ export * from "./core";
1
2
  export * from "./FormoAnalyticsProvider";
2
- export * from "./FormoAnalytics";
3
- export * from "./types";
4
- export { parsePrivyProperties } from "./privy";
5
- export type { PrivyUser, PrivyLinkedAccount, PrivyAccountType, PrivyProfileProperties, PrivyWalletInfo } from "./privy";
6
- export { SolanaManager } from "./solana";
7
- export { SOLANA_CHAIN_IDS, DEFAULT_SOLANA_CHAIN_ID, isSolanaChainId } from "./solana";
8
- export type { SolanaOptions, SolanaCluster, } from "./solana";
9
- export type { SolanaClientStore, SolanaClientState, } from "./solana";
10
3
  //# sourceMappingURL=index.d.ts.map
@@ -1,11 +1,6 @@
1
1
  import { formofy } from "./initialization";
2
+ export * from "./core";
2
3
  export * from "./FormoAnalyticsProvider";
3
- export * from "./FormoAnalytics";
4
- export * from "./types";
5
- export { parsePrivyProperties } from "./privy";
6
- // Solana integration exports
7
- export { SolanaManager } from "./solana";
8
- export { SOLANA_CHAIN_IDS, DEFAULT_SOLANA_CHAIN_ID, isSolanaChainId } from "./solana";
9
4
  if (typeof window !== "undefined")
10
5
  window.formofy = formofy;
11
6
  //# sourceMappingURL=index.js.map
@@ -11,6 +11,7 @@ type Options = {
11
11
  retryCount?: number;
12
12
  errorHandler?: any;
13
13
  maxQueueSize?: number;
14
+ canSend?: () => boolean;
14
15
  };
15
16
  export declare class EventQueue implements IEventQueue {
16
17
  private writeKey;
@@ -21,12 +22,19 @@ export declare class EventQueue implements IEventQueue {
21
22
  private flushIntervalMs;
22
23
  private flushed;
23
24
  private maxQueueSize;
25
+ private queueByteSize;
24
26
  private errorHandler;
25
27
  private retryCount;
26
28
  private pendingFlush;
27
29
  private payloadHashes;
30
+ private canSend?;
28
31
  constructor(writeKey: string, options: Options);
29
32
  private generateMessageId;
33
+ /**
34
+ * Drop all queued data and cancel the flush timer. Called on consent
35
+ * withdrawal / SDK teardown so nothing buffered can be sent later.
36
+ */
37
+ clear(): void;
30
38
  enqueue(event: IFormoEvent, callback?: (...args: any) => void): Promise<void>;
31
39
  flush(callback?: (...args: any) => void, drainAll?: boolean): Promise<void | IFormoEventFlushPayload[]>;
32
40
  /**
@@ -81,6 +81,7 @@ var EventQueue = /** @class */ (function () {
81
81
  function EventQueue(writeKey, options) {
82
82
  var _this = this;
83
83
  this.queue = [];
84
+ this.queueByteSize = 0; // running total of queued items' byteSize
84
85
  this.payloadHashes = new Set();
85
86
  this.onPageLeave = function (callback) {
86
87
  // To ensure the callback is only called once even if more than one events
@@ -136,6 +137,7 @@ var EventQueue = /** @class */ (function () {
136
137
  this.queue = [];
137
138
  this.writeKey = writeKey;
138
139
  this.apiHost = options.apiHost;
140
+ this.canSend = options.canSend;
139
141
  this.retryCount = clampNumber(options.retryCount || DEFAULT_RETRY, MAX_RETRY, MIN_RETRY);
140
142
  this.flushAt = clampNumber(options.flushAt || DEFAULT_FLUSH_AT, MAX_FLUSH_AT, MIN_FLUSH_AT);
141
143
  this.maxQueueSize = clampNumber(options.maxQueueSize || DEFAULT_QUEUE_SIZE, MAX_QUEUE_SIZE, MIN_QUEUE_SIZE);
@@ -169,13 +171,31 @@ var EventQueue = /** @class */ (function () {
169
171
  });
170
172
  });
171
173
  };
174
+ /**
175
+ * Drop all queued data and cancel the flush timer. Called on consent
176
+ * withdrawal / SDK teardown so nothing buffered can be sent later.
177
+ */
178
+ EventQueue.prototype.clear = function () {
179
+ if (this.timer) {
180
+ clearTimeout(this.timer);
181
+ this.timer = null;
182
+ }
183
+ this.queue = [];
184
+ this.queueByteSize = 0;
185
+ this.payloadHashes.clear();
186
+ };
172
187
  EventQueue.prototype.enqueue = function (event, callback) {
173
188
  return __awaiter(this, void 0, void 0, function () {
174
- var message_id, hasReachedFlushAt, hasReachedQueueSize;
189
+ var message_id, queueItem, hasReachedFlushAt, hasReachedQueueSize;
175
190
  return __generator(this, function (_a) {
176
191
  switch (_a.label) {
177
192
  case 0:
178
193
  callback = callback || noop;
194
+ // Refuse to buffer anything once consent is withdrawn.
195
+ if (this.canSend && !this.canSend()) {
196
+ this.clear();
197
+ return [2 /*return*/];
198
+ }
179
199
  return [4 /*yield*/, this.generateMessageId(event)];
180
200
  case 1:
181
201
  message_id = _a.sent();
@@ -184,10 +204,18 @@ var EventQueue = /** @class */ (function () {
184
204
  logger.warn("Event already enqueued, try again after ".concat(millisecondsToSecond(this.flushIntervalMs), " seconds."));
185
205
  return [2 /*return*/];
186
206
  }
187
- this.queue.push({
207
+ queueItem = {
188
208
  message: __assign(__assign({}, event), { message_id: message_id }),
189
209
  callback: callback,
190
- });
210
+ byteSize: 0,
211
+ };
212
+ // Measure once here (message only — JSON.stringify drops the
213
+ // callback function anyway), then track the total incrementally.
214
+ queueItem.byteSize = JSON.stringify({
215
+ message: queueItem.message,
216
+ }).length;
217
+ this.queue.push(queueItem);
218
+ this.queueByteSize += queueItem.byteSize;
191
219
  logger.log("Event enqueued: ".concat(getActionDescriptor(event.type, event.properties)));
192
220
  if (!this.flushed) {
193
221
  this.flushed = true;
@@ -195,8 +223,7 @@ var EventQueue = /** @class */ (function () {
195
223
  return [2 /*return*/];
196
224
  }
197
225
  hasReachedFlushAt = this.queue.length >= this.flushAt;
198
- hasReachedQueueSize = this.queue.reduce(function (acc, item) { return acc + JSON.stringify(item).length; }, 0) >=
199
- this.maxQueueSize;
226
+ hasReachedQueueSize = this.queueByteSize >= this.maxQueueSize;
200
227
  if (hasReachedFlushAt || hasReachedQueueSize) {
201
228
  this.flush();
202
229
  return [2 /*return*/];
@@ -222,6 +249,13 @@ var EventQueue = /** @class */ (function () {
222
249
  clearTimeout(this.timer);
223
250
  this.timer = null;
224
251
  }
252
+ // Final consent gate: a timer/pagehide flush may have been scheduled
253
+ // before opt-out. Drop everything rather than send post-withdrawal.
254
+ if (this.canSend && !this.canSend()) {
255
+ this.clear();
256
+ callback();
257
+ return [2 /*return*/, Promise.resolve()];
258
+ }
225
259
  if (!this.queue.length) {
226
260
  callback();
227
261
  return [2 /*return*/, Promise.resolve()];
@@ -235,11 +269,17 @@ var EventQueue = /** @class */ (function () {
235
269
  case 2:
236
270
  items = this.queue.splice(0, drainAll ? this.queue.length : this.flushAt);
237
271
  // Only remove hashes for flushed items so duplicate detection remains
238
- // active for events still in the queue.
272
+ // active for events still in the queue. Also decrement the running
273
+ // byte total by exactly what left the queue.
239
274
  for (_i = 0, items_1 = items; _i < items_1.length; _i++) {
240
275
  item = items_1[_i];
241
276
  this.payloadHashes.delete(item.message.message_id);
277
+ this.queueByteSize -= item.byteSize;
242
278
  }
279
+ // Re-anchor to the exact invariant when the queue empties, so any
280
+ // accumulated drift can never wedge the size gate.
281
+ if (this.queue.length === 0)
282
+ this.queueByteSize = 0;
243
283
  sentAt = new Date().toISOString();
244
284
  data = items.map(function (item) { return (__assign(__assign({}, item.message), { sent_at: sentAt })); });
245
285
  batches = this.splitIntoBatches(items, data);
@@ -342,6 +382,12 @@ var EventQueue = /** @class */ (function () {
342
382
  case 1:
343
383
  if (!(_i < batches_1.length)) return [3 /*break*/, 6];
344
384
  batch = batches_1[_i];
385
+ // Consent can be withdrawn while a flush is already in flight:
386
+ // batches were spliced before opt-out, and split batches / retry
387
+ // backoff span seconds. Re-check before every send and abandon
388
+ // the remaining batches if consent was revoked mid-flush.
389
+ if (this.canSend && !this.canSend())
390
+ return [3 /*break*/, 6];
345
391
  _a.label = 2;
346
392
  case 2:
347
393
  _a.trys.push([2, 4, , 5]);
@@ -2,5 +2,6 @@ import { IFormoEvent } from "../types";
2
2
  export interface IEventQueue {
3
3
  enqueue(event: IFormoEvent, callback?: (...args: any) => void): Promise<void>;
4
4
  flush(callback?: (...args: any) => void): Promise<any>;
5
+ clear(): void;
5
6
  }
6
7
  //# sourceMappingURL=type.d.ts.map
@@ -4,7 +4,19 @@
4
4
  * Handles tracking of detected wallets and identified wallet-address pairs
5
5
  * using cookies to maintain state across page loads within a session.
6
6
  */
7
+ var __assign = (this && this.__assign) || function () {
8
+ __assign = Object.assign || function(t) {
9
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
10
+ s = arguments[i];
11
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
12
+ t[p] = s[p];
13
+ }
14
+ return t;
15
+ };
16
+ return __assign.apply(this, arguments);
17
+ };
7
18
  import { cookie } from "../storage";
19
+ import { getIdentityCookieSecurity } from "../storage/cookiePolicy";
8
20
  import { logger } from "../logger";
9
21
  /**
10
22
  * Cookie keys for session tracking
@@ -63,11 +75,9 @@ var FormoAnalyticsSession = /** @class */ (function () {
63
75
  if (rdnses.length > MAX_SESSION_ENTRIES) {
64
76
  rdnses.splice(0, rdnses.length - MAX_SESSION_ENTRIES);
65
77
  }
66
- cookie().set(SESSION_WALLET_DETECTED_KEY, rdnses.join(","), {
78
+ cookie().set(SESSION_WALLET_DETECTED_KEY, rdnses.join(","), __assign({
67
79
  // Expires by the end of the day
68
- expires: new Date(Date.now() + 86400 * 1000).toUTCString(),
69
- path: "/",
70
- });
80
+ expires: new Date(Date.now() + 86400 * 1000).toUTCString(), path: "/" }, getIdentityCookieSecurity()));
71
81
  }
72
82
  };
73
83
  /**
@@ -107,11 +117,9 @@ var FormoAnalyticsSession = /** @class */ (function () {
107
117
  identifiedWallets.splice(0, identifiedWallets.length - MAX_SESSION_ENTRIES);
108
118
  }
109
119
  var newValue = identifiedWallets.join(",");
110
- cookie().set(SESSION_WALLET_IDENTIFIED_KEY, newValue, {
120
+ cookie().set(SESSION_WALLET_IDENTIFIED_KEY, newValue, __assign({
111
121
  // Expires by the end of the day
112
- expires: new Date(Date.now() + 86400 * 1000).toUTCString(),
113
- path: "/",
114
- });
122
+ expires: new Date(Date.now() + 86400 * 1000).toUTCString(), path: "/" }, getIdentityCookieSecurity()));
115
123
  logger.debug("Session: Marked wallet as identified", {
116
124
  identifiedKey: identifiedKey,
117
125
  hasRdns: !!rdns,
@@ -7,4 +7,20 @@
7
7
  * @param crossSubdomain Whether cookies should be shared across subdomains.
8
8
  */
9
9
  export declare function getIdentityCookieDomain(crossSubdomain?: boolean): string;
10
+ /**
11
+ * Security attributes for identity/session cookies (user-id,
12
+ * active-wallet, wallet-detected/identified).
13
+ *
14
+ * - `sameSite: "lax"` — first-party analytics identity. Lax keeps the
15
+ * cookie on top-level navigations (so attribution survives a click-in
16
+ * from another site) while blocking it on cross-site subrequests.
17
+ * Strict would silently drop identity on inbound navigation.
18
+ * - `secure` — only on HTTPS. Setting Secure over plain HTTP (local dev)
19
+ * makes the browser reject the cookie outright, so it must be
20
+ * conditional.
21
+ */
22
+ export declare function getIdentityCookieSecurity(): {
23
+ sameSite: "lax";
24
+ secure: boolean;
25
+ };
10
26
  //# sourceMappingURL=cookiePolicy.d.ts.map
@@ -21,4 +21,22 @@ export function getIdentityCookieDomain(crossSubdomain) {
21
21
  var domain = getApexDomain();
22
22
  return domain ? ".".concat(domain) : "";
23
23
  }
24
+ /**
25
+ * Security attributes for identity/session cookies (user-id,
26
+ * active-wallet, wallet-detected/identified).
27
+ *
28
+ * - `sameSite: "lax"` — first-party analytics identity. Lax keeps the
29
+ * cookie on top-level navigations (so attribution survives a click-in
30
+ * from another site) while blocking it on cross-site subrequests.
31
+ * Strict would silently drop identity on inbound navigation.
32
+ * - `secure` — only on HTTPS. Setting Secure over plain HTTP (local dev)
33
+ * makes the browser reject the cookie outright, so it must be
34
+ * conditional.
35
+ */
36
+ export function getIdentityCookieSecurity() {
37
+ var _a;
38
+ var isHttps = typeof window !== "undefined" &&
39
+ ((_a = window.location) === null || _a === void 0 ? void 0 : _a.protocol) === "https:";
40
+ return { sameSite: "lax", secure: isHttps };
41
+ }
24
42
  //# sourceMappingURL=cookiePolicy.js.map
@@ -1,7 +1,6 @@
1
1
  import { LogLevel } from "../logger";
2
2
  import { IFormoEventContext, IFormoEventProperties, SignatureStatus, TransactionStatus } from "./events";
3
3
  import { EIP1193Provider } from "./provider";
4
- import { ReactNode } from "react";
5
4
  import { SolanaOptions } from "../solana/types";
6
5
  export type Nullable<T> = T | null;
7
6
  export type ChainID = number;
@@ -40,7 +39,6 @@ export interface IFormoAnalytics {
40
39
  chainId?: ChainID;
41
40
  address: Address;
42
41
  message: string;
43
- signatureHash?: string;
44
42
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
45
43
  transaction(params: {
46
44
  status: TransactionStatus;
@@ -222,10 +220,4 @@ export interface Options {
222
220
  errorHandler?: (err: Error) => void;
223
221
  ready?: (formo: IFormoAnalytics) => void;
224
222
  }
225
- export interface FormoAnalyticsProviderProps {
226
- writeKey: string;
227
- options?: Options;
228
- disabled?: boolean;
229
- children: ReactNode;
230
- }
231
223
  //# sourceMappingURL=base.d.ts.map
@@ -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.1";
1
+ export declare const version = "1.30.1";
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.29.1';
3
+ export var version = '1.30.1';
4
4
  //# sourceMappingURL=version.js.map
@@ -170,6 +170,11 @@ var WagmiEventHandler = /** @class */ (function () {
170
170
  chainId: chainId,
171
171
  });
172
172
  if (!(status === "disconnected" && prevStatus === "connected")) return [3 /*break*/, 4];
173
+ // Clear central chain state regardless of autocapture so a later
174
+ // event can't carry a stale excluded/!excluded chainId.
175
+ this.formo.syncWalletState({
176
+ chainId: this.trackingState.lastChainId,
177
+ });
173
178
  if (!this.formo.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 3];
174
179
  return [4 /*yield*/, this.formo.disconnect({
175
180
  chainId: this.trackingState.lastChainId,
@@ -187,6 +192,9 @@ var WagmiEventHandler = /** @class */ (function () {
187
192
  if (!(address && chainId !== undefined)) return [3 /*break*/, 6];
188
193
  this.trackingState.lastAddress = address;
189
194
  this.trackingState.lastChainId = chainId;
195
+ // Sync central state unconditionally so tracking.excludeChains
196
+ // is enforced even when connect autocapture is disabled.
197
+ this.formo.syncWalletState({ chainId: chainId, address: address });
190
198
  if (!this.formo.isAutocaptureEnabled("connect")) return [3 /*break*/, 6];
191
199
  connectorName = this.getConnectorName(state);
192
200
  return [4 /*yield*/, this.formo.connect({ chainId: chainId, address: address }, __assign({}, (connectorName && { providerName: connectorName })))];
@@ -235,6 +243,9 @@ var WagmiEventHandler = /** @class */ (function () {
235
243
  address: address,
236
244
  });
237
245
  this.trackingState.lastChainId = chainId;
246
+ // Sync central state unconditionally so a chain switch to an
247
+ // excluded chain is honored even when chain autocapture is disabled.
248
+ this.formo.syncWalletState({ chainId: chainId, address: address });
238
249
  if (!this.formo.isAutocaptureEnabled("chain")) return [3 /*break*/, 4];
239
250
  _a.label = 1;
240
251
  case 1:
@@ -356,14 +367,21 @@ var WagmiEventHandler = /** @class */ (function () {
356
367
  logger.warn("WagmiEventHandler: Transaction receipt query but no hash found");
357
368
  return;
358
369
  }
359
- // Retrieve stored transaction details from BROADCASTED event
360
- // Normalize hash to lowercase for consistent lookup
370
+ // Retrieve stored transaction details from the BROADCASTED event.
371
+ // Normalize hash to lowercase for consistent lookup.
361
372
  var normalizedHash = transactionHash.toLowerCase();
362
373
  var pendingTx = this.pendingTransactions.get(normalizedHash);
363
- // Use the original sender address from BROADCASTED event if available,
364
- // otherwise fall back to current connected address.
365
- // This handles wallet switches between broadcast and confirmation.
366
- var address = (pendingTx === null || pendingTx === void 0 ? void 0 : pendingTx.address) || this.trackingState.lastAddress;
374
+ // Only emit receipt-derived events for a hash we actually observed
375
+ // being broadcast through this handler. The QueryClient is supplied
376
+ // by the host app and its cache can be written by app code or other
377
+ // deps, so without this gate a forged waitForTransactionReceipt
378
+ // entry could fabricate a confirmed/reverted transaction for an
379
+ // arbitrary hash. No pendingTx → not our broadcast → ignore.
380
+ if (!pendingTx) {
381
+ logger.debug("WagmiEventHandler: Receipt for unobserved tx hash; ignoring", { transactionHash: transactionHash });
382
+ return;
383
+ }
384
+ var address = pendingTx.address;
367
385
  if (!address) {
368
386
  logger.warn("WagmiEventHandler: Transaction receipt query but no address available");
369
387
  return;
@@ -371,9 +389,13 @@ var WagmiEventHandler = /** @class */ (function () {
371
389
  try {
372
390
  // Extract receipt data
373
391
  var receipt = state.data;
374
- // Determine transaction status from receipt
375
- // receipt.status is 'success' or 'reverted' in viem
376
- var txStatus = (receipt === null || receipt === void 0 ? void 0 : receipt.status) === "reverted"
392
+ // Only act on an explicit on-chain outcome. A missing/unknown
393
+ // status must NOT be treated as a confirmation.
394
+ if ((receipt === null || receipt === void 0 ? void 0 : receipt.status) !== "success" && (receipt === null || receipt === void 0 ? void 0 : receipt.status) !== "reverted") {
395
+ logger.debug("WagmiEventHandler: Receipt without explicit success/reverted status; ignoring", { transactionHash: transactionHash, status: receipt === null || receipt === void 0 ? void 0 : receipt.status });
396
+ return;
397
+ }
398
+ var txStatus = receipt.status === "reverted"
377
399
  ? TransactionStatus.REVERTED
378
400
  : TransactionStatus.CONFIRMED;
379
401
  logger.info("WagmiEventHandler: Tracking transaction confirmation", {
@@ -452,15 +474,13 @@ var WagmiEventHandler = /** @class */ (function () {
452
474
  return;
453
475
  }
454
476
  try {
455
- // Map Wagmi mutation status to Formo signature status
477
+ // Map Wagmi mutation status to Formo signature status.
456
478
  var status_1;
457
- var signatureHash = void 0;
458
479
  if (state.status === "pending") {
459
480
  status_1 = SignatureStatus.REQUESTED;
460
481
  }
461
482
  else if (state.status === "success") {
462
483
  status_1 = SignatureStatus.CONFIRMED;
463
- signatureHash = state.data;
464
484
  }
465
485
  else if (state.status === "error") {
466
486
  status_1 = SignatureStatus.REJECTED;
@@ -468,13 +488,11 @@ var WagmiEventHandler = /** @class */ (function () {
468
488
  else {
469
489
  return; // Ignore idle state
470
490
  }
471
- // Extract message from variables
472
491
  var message = void 0;
473
492
  if (mutationType === "signMessage") {
474
493
  message = variables.message || "";
475
494
  }
476
495
  else {
477
- // For signTypedData, stringify the typed data
478
496
  message = JSON.stringify(variables.message || variables.types || {});
479
497
  }
480
498
  logger.info("WagmiEventHandler: Tracking signature event", {
@@ -483,7 +501,12 @@ var WagmiEventHandler = /** @class */ (function () {
483
501
  address: address,
484
502
  chainId: chainId,
485
503
  });
486
- this.formo.signature(__assign({ status: status_1, chainId: chainId, address: address, message: message }, (signatureHash && { signatureHash: signatureHash })));
504
+ this.formo.signature({
505
+ status: status_1,
506
+ chainId: chainId,
507
+ address: address,
508
+ message: message,
509
+ });
487
510
  }
488
511
  catch (error) {
489
512
  logger.error("WagmiEventHandler: Error handling signature mutation:", error);