@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
package/README.md CHANGED
@@ -17,12 +17,14 @@
17
17
 
18
18
  ## Installation
19
19
 
20
- The Formo Web SDK is a Javascript library that allows you to track user event data from your website and app.
20
+ The Formo Web SDK is a Javascript library that allows you to track user event data from your website and app.
21
21
 
22
22
  You can install Formo on:
23
- - [Websites](https://docs.formo.so/install#website)
24
- - [React apps](https://docs.formo.so/install#react)
25
- - [Next.js apps](https://docs.formo.so/install#next-js-app-router)
23
+ - [Wagmi](https://docs.formo.so/install#wagmi) (recommended)
24
+ - [HTML Snippet](https://docs.formo.so/install#html-snippet)
25
+ - [React](https://docs.formo.so/install#react)
26
+ - [Next.js app router](https://docs.formo.so/install#next-js-app-router)
27
+ - [Next.js pages router](https://docs.formo.so/install#next-js-pages-router)
26
28
 
27
29
  ## Configuration
28
30
 
@@ -139,7 +139,7 @@ export declare class FormoAnalytics implements IFormoAnalytics {
139
139
  * @param {(...args: unknown[]) => void} callback
140
140
  * @returns {Promise<void>}
141
141
  */
142
- transaction({ status, chainId, address, data, to, value, transactionHash, }: {
142
+ transaction({ status, chainId, address, data, to, value, transactionHash, function_name, function_args, }: {
143
143
  status: TransactionStatus;
144
144
  chainId: ChainID;
145
145
  address: Address;
@@ -147,6 +147,8 @@ export declare class FormoAnalytics implements IFormoAnalytics {
147
147
  to?: string;
148
148
  value?: string;
149
149
  transactionHash?: string;
150
+ function_name?: string;
151
+ function_args?: Record<string, unknown>;
150
152
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
151
153
  /**
152
154
  * Emits an identify event with current wallet address and provider info.
@@ -140,6 +140,7 @@ var FormoAnalytics = /** @class */ (function () {
140
140
  retryCount: options.retryCount,
141
141
  maxQueueSize: options.maxQueueSize,
142
142
  flushInterval: options.flushInterval,
143
+ errorHandler: options.errorHandler,
143
144
  }), options);
144
145
  // Check consent status on initialization
145
146
  if (this.hasOptedOutTracking()) {
@@ -425,10 +426,10 @@ var FormoAnalytics = /** @class */ (function () {
425
426
  */
426
427
  FormoAnalytics.prototype.transaction = function (_a, properties_1, context_1, callback_1) {
427
428
  return __awaiter(this, arguments, void 0, function (_b, properties, context, callback) {
428
- var status = _b.status, chainId = _b.chainId, address = _b.address, data = _b.data, to = _b.to, value = _b.value, transactionHash = _b.transactionHash;
429
+ var status = _b.status, chainId = _b.chainId, address = _b.address, data = _b.data, to = _b.to, value = _b.value, transactionHash = _b.transactionHash, function_name = _b.function_name, function_args = _b.function_args;
429
430
  return __generator(this, function (_c) {
430
431
  switch (_c.label) {
431
- case 0: return [4 /*yield*/, this.trackEvent(constants_1.EventType.TRANSACTION, __assign({ status: status, chainId: chainId, address: address, data: data, to: to, value: value }, (transactionHash && { transactionHash: transactionHash })), properties, context, callback)];
432
+ case 0: return [4 /*yield*/, this.trackEvent(constants_1.EventType.TRANSACTION, __assign(__assign(__assign({ status: status, chainId: chainId, address: address, data: data, to: to, value: value }, (transactionHash && { transactionHash: transactionHash })), (function_name && { function_name: function_name })), (function_args && { function_args: function_args })), properties, context, callback)];
432
433
  case 1:
433
434
  _c.sent();
434
435
  return [2 /*return*/];
@@ -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
  }
@@ -409,12 +409,12 @@ var EventFactory = /** @class */ (function () {
409
409
  });
410
410
  });
411
411
  };
412
- EventFactory.prototype.generateTransactionEvent = function (status, chainId, address, data, to, value, transactionHash, properties, context) {
412
+ EventFactory.prototype.generateTransactionEvent = function (status, chainId, address, data, to, value, transactionHash, function_name, function_args, properties, context) {
413
413
  return __awaiter(this, void 0, void 0, function () {
414
414
  var transactionEvent;
415
415
  return __generator(this, function (_a) {
416
416
  transactionEvent = {
417
- properties: __assign(__assign({ status: status, chainId: chainId, data: data, to: to, value: value }, (transactionHash && { transactionHash: transactionHash })), properties),
417
+ 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),
418
418
  address: address,
419
419
  type: "transaction",
420
420
  };
@@ -493,7 +493,7 @@ var EventFactory = /** @class */ (function () {
493
493
  case 14:
494
494
  formoEvent = _b.sent();
495
495
  return [3 /*break*/, 19];
496
- 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)];
496
+ 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)];
497
497
  case 16:
498
498
  formoEvent = _b.sent();
499
499
  return [3 /*break*/, 19];
@@ -1,3 +1,11 @@
1
- declare const _default: (input: string | Request | URL, 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,8 +1,126 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ 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);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ 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;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __rest = (this && this.__rest) || function (s, e) {
39
+ var t = {};
40
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
41
+ t[p] = s[p];
42
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
43
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
44
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
45
+ t[p[i]] = s[p[i]];
46
+ }
47
+ return t;
4
48
  };
5
49
  Object.defineProperty(exports, "__esModule", { value: true });
6
- var fetch_retry_1 = __importDefault(require("fetch-retry"));
7
- exports.default = (0, fetch_retry_1.default)(global.fetch);
50
+ function fetchWithRetry(input, init) {
51
+ return __awaiter(this, void 0, void 0, function () {
52
+ var _a, _b, retries, retryDelay, retryOn, fetchInit, _loop_1, attempt, state_1;
53
+ return __generator(this, function (_c) {
54
+ switch (_c.label) {
55
+ case 0:
56
+ _a = init || {}, _b = _a.retries, retries = _b === void 0 ? 0 : _b, retryDelay = _a.retryDelay, retryOn = _a.retryOn, fetchInit = __rest(_a, ["retries", "retryDelay", "retryOn"]);
57
+ _loop_1 = function (attempt) {
58
+ var isLastAttempt, response, responseError, error_1;
59
+ return __generator(this, function (_d) {
60
+ switch (_d.label) {
61
+ case 0:
62
+ isLastAttempt = attempt === retries;
63
+ _d.label = 1;
64
+ case 1:
65
+ _d.trys.push([1, 6, , 10]);
66
+ return [4 /*yield*/, globalThis.fetch(input, fetchInit)];
67
+ case 2:
68
+ response = _d.sent();
69
+ if (response.ok) {
70
+ return [2 /*return*/, { value: response }];
71
+ }
72
+ // On the last attempt, return whatever we got
73
+ if (isLastAttempt) {
74
+ return [2 /*return*/, { value: response }];
75
+ }
76
+ responseError = new Error(response.statusText);
77
+ responseError.response = response;
78
+ if (!(retryOn === null || retryOn === void 0 ? void 0 : retryOn(attempt, responseError, response))) return [3 /*break*/, 5];
79
+ if (!retryDelay) return [3 /*break*/, 4];
80
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, retryDelay(attempt)); })];
81
+ case 3:
82
+ _d.sent();
83
+ _d.label = 4;
84
+ case 4: return [2 /*return*/, "continue"];
85
+ case 5: return [2 /*return*/, { value: response }];
86
+ case 6:
87
+ error_1 = _d.sent();
88
+ // On the last attempt, throw immediately
89
+ if (isLastAttempt) {
90
+ throw error_1;
91
+ }
92
+ if (!(retryOn === null || retryOn === void 0 ? void 0 : retryOn(attempt, error_1, null))) return [3 /*break*/, 9];
93
+ if (!retryDelay) return [3 /*break*/, 8];
94
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, retryDelay(attempt)); })];
95
+ case 7:
96
+ _d.sent();
97
+ _d.label = 8;
98
+ case 8: return [2 /*return*/, "continue"];
99
+ case 9: throw error_1;
100
+ case 10: return [2 /*return*/];
101
+ }
102
+ });
103
+ };
104
+ attempt = 0;
105
+ _c.label = 1;
106
+ case 1:
107
+ if (!(attempt <= retries)) return [3 /*break*/, 4];
108
+ return [5 /*yield**/, _loop_1(attempt)];
109
+ case 2:
110
+ state_1 = _c.sent();
111
+ if (typeof state_1 === "object")
112
+ return [2 /*return*/, state_1.value];
113
+ _c.label = 3;
114
+ case 3:
115
+ attempt++;
116
+ return [3 /*break*/, 1];
117
+ case 4:
118
+ // Unreachable — the loop always returns or throws on the last attempt.
119
+ // Required to satisfy TypeScript's control flow analysis.
120
+ throw new Error("Unexpected: retry loop exited without returning or throwing");
121
+ }
122
+ });
123
+ });
124
+ }
125
+ exports.default = fetchWithRetry;
8
126
  //# 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;
@@ -57,6 +57,16 @@ var logger_1 = require("../logger");
57
57
  var constants_1 = require("../constants");
58
58
  var fetch_1 = __importDefault(require("../fetch"));
59
59
  var noop = function () { };
60
+ var safeCall = function (fn) {
61
+ var args = [];
62
+ for (var _i = 1; _i < arguments.length; _i++) {
63
+ args[_i - 1] = arguments[_i];
64
+ }
65
+ try {
66
+ fn.apply(void 0, args);
67
+ }
68
+ catch ( /* swallow */_a) { /* swallow */ }
69
+ };
60
70
  var DEFAULT_RETRY = 3;
61
71
  var MAX_RETRY = 5;
62
72
  var MIN_RETRY = 1;
@@ -66,6 +76,10 @@ var MIN_FLUSH_AT = 1;
66
76
  var DEFAULT_QUEUE_SIZE = 1024 * 500; // 500kB
67
77
  var MAX_QUEUE_SIZE = 1024 * 500; // 500kB
68
78
  var MIN_QUEUE_SIZE = 200; // 200 bytes
79
+ // Browsers enforce a 64KB limit on the total body size of in-flight
80
+ // keepalive fetch requests. Payloads exceeding this are silently cancelled,
81
+ // producing a TypeError: Failed to fetch that cannot be resolved by retrying.
82
+ var KEEPALIVE_PAYLOAD_LIMIT = 64 * 1024; // 64kB
69
83
  var DEFAULT_FLUSH_INTERVAL = 1000 * 30; // 1 MINUTE
70
84
  var MAX_FLUSH_INTERVAL = 1000 * 300; // 5 MINUTES
71
85
  var MIN_FLUSH_INTERVAL = 1000 * 10; // 10 SECONDS
@@ -150,7 +164,6 @@ var EventQueue = /** @class */ (function () {
150
164
  });
151
165
  }); });
152
166
  }
153
- //#region Public functions
154
167
  EventQueue.prototype.generateMessageId = function (event) {
155
168
  return __awaiter(this, void 0, void 0, function () {
156
169
  var formattedTimestamp, eventForHashing, eventString;
@@ -206,7 +219,7 @@ var EventQueue = /** @class */ (function () {
206
219
  };
207
220
  EventQueue.prototype.flush = function (callback) {
208
221
  return __awaiter(this, void 0, void 0, function () {
209
- var err_1, items, sentAt, data, done;
222
+ var items, sentAt, data, batches;
210
223
  var _this = this;
211
224
  return __generator(this, function (_a) {
212
225
  switch (_a.label) {
@@ -220,75 +233,171 @@ var EventQueue = /** @class */ (function () {
220
233
  callback();
221
234
  return [2 /*return*/, Promise.resolve()];
222
235
  }
223
- _a.label = 1;
224
- case 1:
225
- _a.trys.push([1, 4, , 5]);
226
- if (!this.pendingFlush) return [3 /*break*/, 3];
236
+ if (!this.pendingFlush) return [3 /*break*/, 2];
227
237
  return [4 /*yield*/, this.pendingFlush];
228
- case 2:
238
+ case 1:
229
239
  _a.sent();
230
- _a.label = 3;
231
- case 3: return [3 /*break*/, 5];
232
- case 4:
233
- err_1 = _a.sent();
234
- this.pendingFlush = null;
235
- throw err_1;
236
- case 5:
240
+ _a.label = 2;
241
+ case 2:
237
242
  items = this.queue.splice(0, this.flushAt);
238
243
  this.payloadHashes.clear();
239
244
  sentAt = new Date().toISOString();
240
245
  data = items.map(function (item) { return (__assign(__assign({}, item.message), { sent_at: sentAt })); });
241
- done = function (err) {
242
- items.forEach(function (_a) {
243
- var message = _a.message, callback = _a.callback;
244
- return callback(err, message, data);
245
- });
246
- callback(err, data);
247
- };
248
- return [2 /*return*/, (this.pendingFlush = (0, fetch_1.default)("".concat(this.apiHost), {
249
- headers: (0, constants_1.EVENTS_API_REQUEST_HEADER)(this.writeKey),
250
- method: "POST",
251
- body: JSON.stringify(data),
252
- keepalive: true,
253
- retries: this.retryCount,
254
- retryDelay: function (attempt) { return Math.pow(2, attempt) * 1000; }, // exponential backoff
255
- retryOn: function (_, error) { return _this.isErrorRetryable(error); },
256
- })
257
- .then(function () {
258
- done();
246
+ batches = this.splitIntoBatches(items, data);
247
+ return [2 /*return*/, (this.pendingFlush = this.sendBatches(batches, data)
248
+ .then(function (firstError) {
249
+ if (firstError) {
250
+ safeCall(callback, firstError, data);
251
+ if (typeof _this.errorHandler === "function") {
252
+ safeCall(_this.errorHandler, firstError);
253
+ }
254
+ }
255
+ else {
256
+ safeCall(callback, undefined, data);
257
+ }
259
258
  return Promise.resolve(data);
260
259
  })
261
260
  .catch(function (err) {
261
+ // Defensive: should not be reachable since sendBatches catches
262
+ // all errors internally, but guard against unexpected failures.
263
+ safeCall(callback, err, data);
262
264
  if (typeof _this.errorHandler === "function") {
263
- done(err);
264
- return _this.errorHandler(err);
265
- }
266
- if (err.response) {
267
- var error = new Error(err.response.statusText);
268
- done(error);
269
- throw error;
265
+ safeCall(_this.errorHandler, err);
270
266
  }
271
- done(err);
272
- throw err;
267
+ // Do NOT re-throw — analytics errors should never
268
+ // propagate as unhandled rejections to the host app
273
269
  }))];
274
270
  }
275
271
  });
276
272
  });
277
273
  };
278
- //#region Utility functions
279
- EventQueue.prototype.isErrorRetryable = function (error) {
280
- var _a, _b, _c;
274
+ /**
275
+ * Returns the UTF-8 byte length of a string. The browser's keepalive limit
276
+ * is enforced on the wire (UTF-8 bytes), not on JS string length (UTF-16
277
+ * code units). Non-ASCII characters (CJK, emoji) can be 2–4x larger in
278
+ * UTF-8 than their string .length suggests.
279
+ */
280
+ EventQueue.byteLength = function (str) {
281
+ return new TextEncoder().encode(str).byteLength;
282
+ };
283
+ /**
284
+ * Splits events into batches that respect the browser's 64KB keepalive
285
+ * payload size limit. Each batch pairs its serialized data with the
286
+ * original queue items (for per-item callback reporting) and a flag
287
+ * indicating whether keepalive is safe to use.
288
+ */
289
+ EventQueue.prototype.splitIntoBatches = function (items, data) {
290
+ var serialized = JSON.stringify(data);
291
+ if (EventQueue.byteLength(serialized) <= KEEPALIVE_PAYLOAD_LIMIT) {
292
+ return [{ data: data, items: items, keepalive: true }];
293
+ }
294
+ var batches = [];
295
+ var currentData = [];
296
+ var currentItems = [];
297
+ var currentSize = 2; // account for JSON array brackets "[]"
298
+ for (var i = 0; i < data.length; i++) {
299
+ var event_1 = data[i];
300
+ var eventSize = EventQueue.byteLength(JSON.stringify(event_1));
301
+ var sizeWithEvent = currentSize + (currentData.length > 0 ? 1 : 0) + eventSize;
302
+ if (sizeWithEvent > KEEPALIVE_PAYLOAD_LIMIT) {
303
+ if (currentData.length > 0) {
304
+ batches.push({ data: currentData, items: currentItems, keepalive: true });
305
+ }
306
+ // If a single event exceeds the limit, send it without keepalive
307
+ if (eventSize + 2 > KEEPALIVE_PAYLOAD_LIMIT) {
308
+ batches.push({ data: [event_1], items: [items[i]], keepalive: false });
309
+ currentData = [];
310
+ currentItems = [];
311
+ currentSize = 2;
312
+ }
313
+ else {
314
+ currentData = [event_1];
315
+ currentItems = [items[i]];
316
+ currentSize = 2 + eventSize;
317
+ }
318
+ }
319
+ else {
320
+ currentData.push(event_1);
321
+ currentItems.push(items[i]);
322
+ currentSize = sizeWithEvent;
323
+ }
324
+ }
325
+ if (currentData.length > 0) {
326
+ batches.push({ data: currentData, items: currentItems, keepalive: true });
327
+ }
328
+ return batches;
329
+ };
330
+ /**
331
+ * Sends batches sequentially, notifying per-item callbacks on success/failure.
332
+ * Returns the first error encountered (if any) so the caller can report it.
333
+ */
334
+ EventQueue.prototype.sendBatches = function (batches, allData) {
335
+ return __awaiter(this, void 0, void 0, function () {
336
+ var firstError, _i, batches_1, batch, body, response, error, err_1;
337
+ var _this = this;
338
+ return __generator(this, function (_a) {
339
+ switch (_a.label) {
340
+ case 0:
341
+ _i = 0, batches_1 = batches;
342
+ _a.label = 1;
343
+ case 1:
344
+ if (!(_i < batches_1.length)) return [3 /*break*/, 6];
345
+ batch = batches_1[_i];
346
+ _a.label = 2;
347
+ case 2:
348
+ _a.trys.push([2, 4, , 5]);
349
+ body = JSON.stringify(batch.data);
350
+ return [4 /*yield*/, (0, fetch_1.default)("".concat(this.apiHost), {
351
+ headers: (0, constants_1.EVENTS_API_REQUEST_HEADER)(this.writeKey),
352
+ method: "POST",
353
+ body: body,
354
+ keepalive: batch.keepalive,
355
+ retries: this.retryCount,
356
+ retryDelay: function (attempt) { return Math.pow(2, attempt) * 1000; },
357
+ retryOn: function (_, error, response) { return _this.isErrorRetryable(error, response); },
358
+ })];
359
+ case 3:
360
+ response = _a.sent();
361
+ if (!response.ok) {
362
+ error = new Error(response.statusText || "HTTP ".concat(response.status));
363
+ error.response = response;
364
+ throw error;
365
+ }
366
+ batch.items.forEach(function (_a) {
367
+ var message = _a.message, cb = _a.callback;
368
+ return safeCall(cb, undefined, message, allData);
369
+ });
370
+ return [3 /*break*/, 5];
371
+ case 4:
372
+ err_1 = _a.sent();
373
+ firstError = firstError || err_1;
374
+ batch.items.forEach(function (_a) {
375
+ var message = _a.message, cb = _a.callback;
376
+ return safeCall(cb, err_1, message, allData);
377
+ });
378
+ return [3 /*break*/, 5];
379
+ case 5:
380
+ _i++;
381
+ return [3 /*break*/, 1];
382
+ case 6: return [2 /*return*/, firstError];
383
+ }
384
+ });
385
+ });
386
+ };
387
+ EventQueue.prototype.isErrorRetryable = function (error, response) {
388
+ var _a, _b;
281
389
  // Retry Network Errors.
282
- if ((0, validators_1.isNetworkError)(error))
390
+ if (error && (0, validators_1.isNetworkError)(error))
283
391
  return true;
284
- // Cannot determine if the request can be retried
285
- if (!(error === null || error === void 0 ? void 0 : error.response))
392
+ // Check response status if available
393
+ 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;
394
+ if (!status)
286
395
  return false;
287
396
  // Retry Server Errors (5xx).
288
- 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)
397
+ if (status >= 500 && status <= 599)
289
398
  return true;
290
399
  // Retry if rate limited.
291
- if (((_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.status) === 429)
400
+ if (status === 429)
292
401
  return true;
293
402
  return false;
294
403
  };
@@ -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
@@ -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.26.0';
6
+ exports.version = '1.27.0';
7
7
  //# 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
  */