@formo/analytics 1.26.0 → 1.27.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 (37) hide show
  1. package/README.md +6 -4
  2. package/dist/cjs/src/FormoAnalytics.d.ts +3 -1
  3. package/dist/cjs/src/FormoAnalytics.js +3 -2
  4. package/dist/cjs/src/event/EventFactory.d.ts +1 -1
  5. package/dist/cjs/src/event/EventFactory.js +3 -3
  6. package/dist/cjs/src/fetch/index.d.ts +10 -2
  7. package/dist/cjs/src/fetch/index.js +122 -4
  8. package/dist/cjs/src/queue/EventQueue.d.ts +24 -2
  9. package/dist/cjs/src/queue/EventQueue.js +158 -49
  10. package/dist/cjs/src/types/base.d.ts +8 -0
  11. package/dist/cjs/src/types/events.d.ts +5 -3
  12. package/dist/cjs/src/version.d.ts +1 -1
  13. package/dist/cjs/src/version.js +1 -1
  14. package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +24 -0
  15. package/dist/cjs/src/wagmi/WagmiEventHandler.js +236 -16
  16. package/dist/cjs/src/wagmi/types.d.ts +31 -0
  17. package/dist/cjs/src/wagmi/utils.d.ts +74 -0
  18. package/dist/cjs/src/wagmi/utils.js +198 -0
  19. package/dist/esm/src/FormoAnalytics.d.ts +3 -1
  20. package/dist/esm/src/FormoAnalytics.js +3 -2
  21. package/dist/esm/src/event/EventFactory.d.ts +1 -1
  22. package/dist/esm/src/event/EventFactory.js +3 -3
  23. package/dist/esm/src/fetch/index.d.ts +10 -2
  24. package/dist/esm/src/fetch/index.js +123 -2
  25. package/dist/esm/src/queue/EventQueue.d.ts +24 -2
  26. package/dist/esm/src/queue/EventQueue.js +158 -49
  27. package/dist/esm/src/types/base.d.ts +8 -0
  28. package/dist/esm/src/types/events.d.ts +5 -3
  29. package/dist/esm/src/version.d.ts +1 -1
  30. package/dist/esm/src/version.js +1 -1
  31. package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +24 -0
  32. package/dist/esm/src/wagmi/WagmiEventHandler.js +236 -16
  33. package/dist/esm/src/wagmi/types.d.ts +31 -0
  34. package/dist/esm/src/wagmi/utils.d.ts +74 -0
  35. package/dist/esm/src/wagmi/utils.js +192 -0
  36. package/dist/index.umd.min.js +1 -1
  37. package/package.json +6 -2
@@ -27,7 +27,7 @@ declare class EventFactory implements IEventFactory {
27
27
  generateDisconnectEvent(chainId?: ChainID, address?: Address, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
28
28
  generateChainChangedEvent(chainId: ChainID, address: Address, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
29
29
  generateSignatureEvent(status: SignatureStatus, chainId: ChainID, address: Address, message: string, signatureHash?: string, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
30
- generateTransactionEvent(status: TransactionStatus, chainId: ChainID, address: Address, data: string, to: string, value: string, transactionHash?: string, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
30
+ generateTransactionEvent(status: TransactionStatus, chainId: ChainID, address: Address, data?: string, to?: string, value?: string, transactionHash?: string, function_name?: string, function_args?: Record<string, unknown>, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
31
31
  generateTrackEvent(event: string, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
32
32
  create(event: APIEvent, address?: Address, userId?: string): Promise<IFormoEvent>;
33
33
  }
@@ -403,12 +403,12 @@ var EventFactory = /** @class */ (function () {
403
403
  });
404
404
  });
405
405
  };
406
- EventFactory.prototype.generateTransactionEvent = function (status, chainId, address, data, to, value, transactionHash, properties, context) {
406
+ EventFactory.prototype.generateTransactionEvent = function (status, chainId, address, data, to, value, transactionHash, function_name, function_args, properties, context) {
407
407
  return __awaiter(this, void 0, void 0, function () {
408
408
  var transactionEvent;
409
409
  return __generator(this, function (_a) {
410
410
  transactionEvent = {
411
- properties: __assign(__assign({ status: status, chainId: chainId, data: data, to: to, value: value }, (transactionHash && { transactionHash: transactionHash })), properties),
411
+ properties: __assign(__assign(__assign(__assign(__assign(__assign(__assign({ status: status, chainId: chainId }, (data && { data: data })), (to && { to: to })), (value && { value: value })), (transactionHash && { transactionHash: transactionHash })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), properties),
412
412
  address: address,
413
413
  type: "transaction",
414
414
  };
@@ -487,7 +487,7 @@ var EventFactory = /** @class */ (function () {
487
487
  case 14:
488
488
  formoEvent = _b.sent();
489
489
  return [3 /*break*/, 19];
490
- case 15: return [4 /*yield*/, this.generateTransactionEvent(event.status, event.chainId, event.address, event.data, event.to, event.value, event.transactionHash, event.properties, event.context)];
490
+ case 15: return [4 /*yield*/, this.generateTransactionEvent(event.status, event.chainId, event.address, event.data, event.to, event.value, event.transactionHash, event.function_name, event.function_args, event.properties, event.context)];
491
491
  case 16:
492
492
  formoEvent = _b.sent();
493
493
  return [3 /*break*/, 19];
@@ -1,3 +1,11 @@
1
- declare const _default: (input: string | URL | Request, init?: (RequestInit & import("fetch-retry").RequestInitRetryParams<typeof globalThis.fetch>) | undefined) => Promise<Response>;
2
- export default _default;
1
+ export interface FetchRetryError extends Error {
2
+ response?: Response;
3
+ }
4
+ type RetryOptions = {
5
+ retries?: number;
6
+ retryDelay?: (attempt: number) => number;
7
+ retryOn?: (attempt: number, error: FetchRetryError | null, response: Response | null) => boolean;
8
+ };
9
+ declare function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit & RetryOptions): Promise<Response>;
10
+ export default fetchWithRetry;
3
11
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,124 @@
1
- import fetch from "fetch-retry";
2
- export default fetch(global.fetch);
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ var __rest = (this && this.__rest) || function (s, e) {
38
+ var t = {};
39
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
40
+ t[p] = s[p];
41
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
42
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
43
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
44
+ t[p[i]] = s[p[i]];
45
+ }
46
+ return t;
47
+ };
48
+ function fetchWithRetry(input, init) {
49
+ return __awaiter(this, void 0, void 0, function () {
50
+ var _a, _b, retries, retryDelay, retryOn, fetchInit, _loop_1, attempt, state_1;
51
+ return __generator(this, function (_c) {
52
+ switch (_c.label) {
53
+ case 0:
54
+ _a = init || {}, _b = _a.retries, retries = _b === void 0 ? 0 : _b, retryDelay = _a.retryDelay, retryOn = _a.retryOn, fetchInit = __rest(_a, ["retries", "retryDelay", "retryOn"]);
55
+ _loop_1 = function (attempt) {
56
+ var isLastAttempt, response, responseError, error_1;
57
+ return __generator(this, function (_d) {
58
+ switch (_d.label) {
59
+ case 0:
60
+ isLastAttempt = attempt === retries;
61
+ _d.label = 1;
62
+ case 1:
63
+ _d.trys.push([1, 6, , 10]);
64
+ return [4 /*yield*/, globalThis.fetch(input, fetchInit)];
65
+ case 2:
66
+ response = _d.sent();
67
+ if (response.ok) {
68
+ return [2 /*return*/, { value: response }];
69
+ }
70
+ // On the last attempt, return whatever we got
71
+ if (isLastAttempt) {
72
+ return [2 /*return*/, { value: response }];
73
+ }
74
+ responseError = new Error(response.statusText);
75
+ responseError.response = response;
76
+ if (!(retryOn === null || retryOn === void 0 ? void 0 : retryOn(attempt, responseError, response))) return [3 /*break*/, 5];
77
+ if (!retryDelay) return [3 /*break*/, 4];
78
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, retryDelay(attempt)); })];
79
+ case 3:
80
+ _d.sent();
81
+ _d.label = 4;
82
+ case 4: return [2 /*return*/, "continue"];
83
+ case 5: return [2 /*return*/, { value: response }];
84
+ case 6:
85
+ error_1 = _d.sent();
86
+ // On the last attempt, throw immediately
87
+ if (isLastAttempt) {
88
+ throw error_1;
89
+ }
90
+ if (!(retryOn === null || retryOn === void 0 ? void 0 : retryOn(attempt, error_1, null))) return [3 /*break*/, 9];
91
+ if (!retryDelay) return [3 /*break*/, 8];
92
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, retryDelay(attempt)); })];
93
+ case 7:
94
+ _d.sent();
95
+ _d.label = 8;
96
+ case 8: return [2 /*return*/, "continue"];
97
+ case 9: throw error_1;
98
+ case 10: return [2 /*return*/];
99
+ }
100
+ });
101
+ };
102
+ attempt = 0;
103
+ _c.label = 1;
104
+ case 1:
105
+ if (!(attempt <= retries)) return [3 /*break*/, 4];
106
+ return [5 /*yield**/, _loop_1(attempt)];
107
+ case 2:
108
+ state_1 = _c.sent();
109
+ if (typeof state_1 === "object")
110
+ return [2 /*return*/, state_1.value];
111
+ _c.label = 3;
112
+ case 3:
113
+ attempt++;
114
+ return [3 /*break*/, 1];
115
+ case 4:
116
+ // Unreachable — the loop always returns or throws on the last attempt.
117
+ // Required to satisfy TypeScript's control flow analysis.
118
+ throw new Error("Unexpected: retry loop exited without returning or throwing");
119
+ }
120
+ });
121
+ });
122
+ }
123
+ export default fetchWithRetry;
3
124
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,8 @@
1
- import { IFormoEvent } from "../types";
1
+ import { IFormoEvent, IFormoEventPayload } from "../types";
2
2
  import { IEventQueue } from "./type";
3
+ type IFormoEventFlushPayload = IFormoEventPayload & {
4
+ sent_at: string;
5
+ };
3
6
  type Options = {
4
7
  apiHost: string;
5
8
  flushAt?: number;
@@ -25,7 +28,26 @@ export declare class EventQueue implements IEventQueue {
25
28
  constructor(writeKey: string, options: Options);
26
29
  private generateMessageId;
27
30
  enqueue(event: IFormoEvent, callback?: (...args: any) => void): Promise<void>;
28
- flush(callback?: (...args: any) => void): Promise<any>;
31
+ flush(callback?: (...args: any) => void): Promise<void | IFormoEventFlushPayload[]>;
32
+ /**
33
+ * Returns the UTF-8 byte length of a string. The browser's keepalive limit
34
+ * is enforced on the wire (UTF-8 bytes), not on JS string length (UTF-16
35
+ * code units). Non-ASCII characters (CJK, emoji) can be 2–4x larger in
36
+ * UTF-8 than their string .length suggests.
37
+ */
38
+ private static byteLength;
39
+ /**
40
+ * Splits events into batches that respect the browser's 64KB keepalive
41
+ * payload size limit. Each batch pairs its serialized data with the
42
+ * original queue items (for per-item callback reporting) and a flag
43
+ * indicating whether keepalive is safe to use.
44
+ */
45
+ private splitIntoBatches;
46
+ /**
47
+ * Sends batches sequentially, notifying per-item callbacks on success/failure.
48
+ * Returns the first error encountered (if any) so the caller can report it.
49
+ */
50
+ private sendBatches;
29
51
  private isErrorRetryable;
30
52
  private isDuplicate;
31
53
  private onPageLeave;
@@ -51,6 +51,16 @@ import { logger } from "../logger";
51
51
  import { EVENTS_API_REQUEST_HEADER } from "../constants";
52
52
  import fetch from "../fetch";
53
53
  var noop = function () { };
54
+ var safeCall = function (fn) {
55
+ var args = [];
56
+ for (var _i = 1; _i < arguments.length; _i++) {
57
+ args[_i - 1] = arguments[_i];
58
+ }
59
+ try {
60
+ fn.apply(void 0, args);
61
+ }
62
+ catch ( /* swallow */_a) { /* swallow */ }
63
+ };
54
64
  var DEFAULT_RETRY = 3;
55
65
  var MAX_RETRY = 5;
56
66
  var MIN_RETRY = 1;
@@ -60,6 +70,10 @@ var MIN_FLUSH_AT = 1;
60
70
  var DEFAULT_QUEUE_SIZE = 1024 * 500; // 500kB
61
71
  var MAX_QUEUE_SIZE = 1024 * 500; // 500kB
62
72
  var MIN_QUEUE_SIZE = 200; // 200 bytes
73
+ // Browsers enforce a 64KB limit on the total body size of in-flight
74
+ // keepalive fetch requests. Payloads exceeding this are silently cancelled,
75
+ // producing a TypeError: Failed to fetch that cannot be resolved by retrying.
76
+ var KEEPALIVE_PAYLOAD_LIMIT = 64 * 1024; // 64kB
63
77
  var DEFAULT_FLUSH_INTERVAL = 1000 * 30; // 1 MINUTE
64
78
  var MAX_FLUSH_INTERVAL = 1000 * 300; // 5 MINUTES
65
79
  var MIN_FLUSH_INTERVAL = 1000 * 10; // 10 SECONDS
@@ -144,7 +158,6 @@ var EventQueue = /** @class */ (function () {
144
158
  });
145
159
  }); });
146
160
  }
147
- //#region Public functions
148
161
  EventQueue.prototype.generateMessageId = function (event) {
149
162
  return __awaiter(this, void 0, void 0, function () {
150
163
  var formattedTimestamp, eventForHashing, eventString;
@@ -200,7 +213,7 @@ var EventQueue = /** @class */ (function () {
200
213
  };
201
214
  EventQueue.prototype.flush = function (callback) {
202
215
  return __awaiter(this, void 0, void 0, function () {
203
- var err_1, items, sentAt, data, done;
216
+ var items, sentAt, data, batches;
204
217
  var _this = this;
205
218
  return __generator(this, function (_a) {
206
219
  switch (_a.label) {
@@ -214,75 +227,171 @@ var EventQueue = /** @class */ (function () {
214
227
  callback();
215
228
  return [2 /*return*/, Promise.resolve()];
216
229
  }
217
- _a.label = 1;
218
- case 1:
219
- _a.trys.push([1, 4, , 5]);
220
- if (!this.pendingFlush) return [3 /*break*/, 3];
230
+ if (!this.pendingFlush) return [3 /*break*/, 2];
221
231
  return [4 /*yield*/, this.pendingFlush];
222
- case 2:
232
+ case 1:
223
233
  _a.sent();
224
- _a.label = 3;
225
- case 3: return [3 /*break*/, 5];
226
- case 4:
227
- err_1 = _a.sent();
228
- this.pendingFlush = null;
229
- throw err_1;
230
- case 5:
234
+ _a.label = 2;
235
+ case 2:
231
236
  items = this.queue.splice(0, this.flushAt);
232
237
  this.payloadHashes.clear();
233
238
  sentAt = new Date().toISOString();
234
239
  data = items.map(function (item) { return (__assign(__assign({}, item.message), { sent_at: sentAt })); });
235
- done = function (err) {
236
- items.forEach(function (_a) {
237
- var message = _a.message, callback = _a.callback;
238
- return callback(err, message, data);
239
- });
240
- callback(err, data);
241
- };
242
- return [2 /*return*/, (this.pendingFlush = fetch("".concat(this.apiHost), {
243
- headers: EVENTS_API_REQUEST_HEADER(this.writeKey),
244
- method: "POST",
245
- body: JSON.stringify(data),
246
- keepalive: true,
247
- retries: this.retryCount,
248
- retryDelay: function (attempt) { return Math.pow(2, attempt) * 1000; }, // exponential backoff
249
- retryOn: function (_, error) { return _this.isErrorRetryable(error); },
250
- })
251
- .then(function () {
252
- done();
240
+ batches = this.splitIntoBatches(items, data);
241
+ return [2 /*return*/, (this.pendingFlush = this.sendBatches(batches, data)
242
+ .then(function (firstError) {
243
+ if (firstError) {
244
+ safeCall(callback, firstError, data);
245
+ if (typeof _this.errorHandler === "function") {
246
+ safeCall(_this.errorHandler, firstError);
247
+ }
248
+ }
249
+ else {
250
+ safeCall(callback, undefined, data);
251
+ }
253
252
  return Promise.resolve(data);
254
253
  })
255
254
  .catch(function (err) {
255
+ // Defensive: should not be reachable since sendBatches catches
256
+ // all errors internally, but guard against unexpected failures.
257
+ safeCall(callback, err, data);
256
258
  if (typeof _this.errorHandler === "function") {
257
- done(err);
258
- return _this.errorHandler(err);
259
- }
260
- if (err.response) {
261
- var error = new Error(err.response.statusText);
262
- done(error);
263
- throw error;
259
+ safeCall(_this.errorHandler, err);
264
260
  }
265
- done(err);
266
- throw err;
261
+ // Do NOT re-throw — analytics errors should never
262
+ // propagate as unhandled rejections to the host app
267
263
  }))];
268
264
  }
269
265
  });
270
266
  });
271
267
  };
272
- //#region Utility functions
273
- EventQueue.prototype.isErrorRetryable = function (error) {
274
- var _a, _b, _c;
268
+ /**
269
+ * Returns the UTF-8 byte length of a string. The browser's keepalive limit
270
+ * is enforced on the wire (UTF-8 bytes), not on JS string length (UTF-16
271
+ * code units). Non-ASCII characters (CJK, emoji) can be 2–4x larger in
272
+ * UTF-8 than their string .length suggests.
273
+ */
274
+ EventQueue.byteLength = function (str) {
275
+ return new TextEncoder().encode(str).byteLength;
276
+ };
277
+ /**
278
+ * Splits events into batches that respect the browser's 64KB keepalive
279
+ * payload size limit. Each batch pairs its serialized data with the
280
+ * original queue items (for per-item callback reporting) and a flag
281
+ * indicating whether keepalive is safe to use.
282
+ */
283
+ EventQueue.prototype.splitIntoBatches = function (items, data) {
284
+ var serialized = JSON.stringify(data);
285
+ if (EventQueue.byteLength(serialized) <= KEEPALIVE_PAYLOAD_LIMIT) {
286
+ return [{ data: data, items: items, keepalive: true }];
287
+ }
288
+ var batches = [];
289
+ var currentData = [];
290
+ var currentItems = [];
291
+ var currentSize = 2; // account for JSON array brackets "[]"
292
+ for (var i = 0; i < data.length; i++) {
293
+ var event_1 = data[i];
294
+ var eventSize = EventQueue.byteLength(JSON.stringify(event_1));
295
+ var sizeWithEvent = currentSize + (currentData.length > 0 ? 1 : 0) + eventSize;
296
+ if (sizeWithEvent > KEEPALIVE_PAYLOAD_LIMIT) {
297
+ if (currentData.length > 0) {
298
+ batches.push({ data: currentData, items: currentItems, keepalive: true });
299
+ }
300
+ // If a single event exceeds the limit, send it without keepalive
301
+ if (eventSize + 2 > KEEPALIVE_PAYLOAD_LIMIT) {
302
+ batches.push({ data: [event_1], items: [items[i]], keepalive: false });
303
+ currentData = [];
304
+ currentItems = [];
305
+ currentSize = 2;
306
+ }
307
+ else {
308
+ currentData = [event_1];
309
+ currentItems = [items[i]];
310
+ currentSize = 2 + eventSize;
311
+ }
312
+ }
313
+ else {
314
+ currentData.push(event_1);
315
+ currentItems.push(items[i]);
316
+ currentSize = sizeWithEvent;
317
+ }
318
+ }
319
+ if (currentData.length > 0) {
320
+ batches.push({ data: currentData, items: currentItems, keepalive: true });
321
+ }
322
+ return batches;
323
+ };
324
+ /**
325
+ * Sends batches sequentially, notifying per-item callbacks on success/failure.
326
+ * Returns the first error encountered (if any) so the caller can report it.
327
+ */
328
+ EventQueue.prototype.sendBatches = function (batches, allData) {
329
+ return __awaiter(this, void 0, void 0, function () {
330
+ var firstError, _i, batches_1, batch, body, response, error, err_1;
331
+ var _this = this;
332
+ return __generator(this, function (_a) {
333
+ switch (_a.label) {
334
+ case 0:
335
+ _i = 0, batches_1 = batches;
336
+ _a.label = 1;
337
+ case 1:
338
+ if (!(_i < batches_1.length)) return [3 /*break*/, 6];
339
+ batch = batches_1[_i];
340
+ _a.label = 2;
341
+ case 2:
342
+ _a.trys.push([2, 4, , 5]);
343
+ body = JSON.stringify(batch.data);
344
+ return [4 /*yield*/, fetch("".concat(this.apiHost), {
345
+ headers: EVENTS_API_REQUEST_HEADER(this.writeKey),
346
+ method: "POST",
347
+ body: body,
348
+ keepalive: batch.keepalive,
349
+ retries: this.retryCount,
350
+ retryDelay: function (attempt) { return Math.pow(2, attempt) * 1000; },
351
+ retryOn: function (_, error, response) { return _this.isErrorRetryable(error, response); },
352
+ })];
353
+ case 3:
354
+ response = _a.sent();
355
+ if (!response.ok) {
356
+ error = new Error(response.statusText || "HTTP ".concat(response.status));
357
+ error.response = response;
358
+ throw error;
359
+ }
360
+ batch.items.forEach(function (_a) {
361
+ var message = _a.message, cb = _a.callback;
362
+ return safeCall(cb, undefined, message, allData);
363
+ });
364
+ return [3 /*break*/, 5];
365
+ case 4:
366
+ err_1 = _a.sent();
367
+ firstError = firstError || err_1;
368
+ batch.items.forEach(function (_a) {
369
+ var message = _a.message, cb = _a.callback;
370
+ return safeCall(cb, err_1, message, allData);
371
+ });
372
+ return [3 /*break*/, 5];
373
+ case 5:
374
+ _i++;
375
+ return [3 /*break*/, 1];
376
+ case 6: return [2 /*return*/, firstError];
377
+ }
378
+ });
379
+ });
380
+ };
381
+ EventQueue.prototype.isErrorRetryable = function (error, response) {
382
+ var _a, _b;
275
383
  // Retry Network Errors.
276
- if (isNetworkError(error))
384
+ if (error && isNetworkError(error))
277
385
  return true;
278
- // Cannot determine if the request can be retried
279
- if (!(error === null || error === void 0 ? void 0 : error.response))
386
+ // Check response status if available
387
+ var status = (_a = response === null || response === void 0 ? void 0 : response.status) !== null && _a !== void 0 ? _a : (_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status;
388
+ if (!status)
280
389
  return false;
281
390
  // Retry Server Errors (5xx).
282
- if (((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) >= 500 && ((_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status) <= 599)
391
+ if (status >= 500 && status <= 599)
283
392
  return true;
284
393
  // Retry if rate limited.
285
- if (((_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.status) === 429)
394
+ if (status === 429)
286
395
  return true;
287
396
  return false;
288
397
  };
@@ -41,6 +41,8 @@ export interface IFormoAnalytics {
41
41
  to?: string;
42
42
  value?: string;
43
43
  transactionHash?: string;
44
+ function_name?: string;
45
+ function_args?: Record<string, unknown>;
44
46
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
45
47
  identify(params: {
46
48
  address: Address;
@@ -172,6 +174,12 @@ export interface Options {
172
174
  * @example { queryParams: ["via"], pathPattern: "/r/([^/]+)" }
173
175
  */
174
176
  referral?: ReferralOptions;
177
+ /**
178
+ * Optional error handler for analytics network failures.
179
+ * Called when event flush fails after all retries.
180
+ * If not provided, errors are silently swallowed.
181
+ */
182
+ errorHandler?: (err: Error) => void;
175
183
  ready?: (formo: IFormoAnalytics) => void;
176
184
  }
177
185
  export interface FormoAnalyticsProviderProps {
@@ -59,10 +59,12 @@ export interface TransactionAPIEvent {
59
59
  status: TransactionStatus;
60
60
  chainId: ChainID;
61
61
  address: Address;
62
- data: string;
63
- to: string;
64
- value: string;
62
+ data?: string;
63
+ to?: string;
64
+ value?: string;
65
65
  transactionHash?: string;
66
+ function_name?: string;
67
+ function_args?: Record<string, unknown>;
66
68
  }
67
69
  export interface SignatureAPIEvent {
68
70
  type: "signature";
@@ -1,2 +1,2 @@
1
- export declare const version = "1.26.0";
1
+ export declare const version = "1.27.0";
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.26.0';
3
+ export var version = '1.27.0';
4
4
  //# sourceMappingURL=version.js.map
@@ -18,6 +18,16 @@ export declare class WagmiEventHandler {
18
18
  * Key format: `${mutationId}:${status}`
19
19
  */
20
20
  private processedMutations;
21
+ /**
22
+ * Track processed query states to prevent duplicate event emissions
23
+ * Key format: `${queryHash}:${status}`
24
+ */
25
+ private processedQueries;
26
+ /**
27
+ * Store transaction details from BROADCASTED events for use in CONFIRMED/REVERTED
28
+ * Key: transactionHash, Value: transaction details including the original sender address
29
+ */
30
+ private pendingTransactions;
21
31
  constructor(formoAnalytics: FormoAnalytics, wagmiConfig: WagmiConfig, queryClient?: QueryClient);
22
32
  /**
23
33
  * Set up listeners for wallet connection, disconnection, and chain changes
@@ -35,6 +45,20 @@ export declare class WagmiEventHandler {
35
45
  * Set up mutation tracking for signatures and transactions
36
46
  */
37
47
  private setupMutationTracking;
48
+ /**
49
+ * Set up query tracking for transaction confirmations
50
+ * Listens for waitForTransactionReceipt queries to detect CONFIRMED status
51
+ */
52
+ private setupQueryTracking;
53
+ /**
54
+ * Handle query cache events (transaction confirmations)
55
+ */
56
+ private handleQueryEvent;
57
+ /**
58
+ * Handle waitForTransactionReceipt query completion
59
+ * Emits CONFIRMED or REVERTED transaction status
60
+ */
61
+ private handleTransactionReceiptQuery;
38
62
  /**
39
63
  * Handle mutation cache events (signatures, transactions)
40
64
  */