@formo/analytics 1.12.0-alpha.2 → 1.12.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 (67) hide show
  1. package/.env.example +1 -0
  2. package/CONTRIBUTING.md +93 -0
  3. package/README.md +4 -163
  4. package/dist/cjs/src/FormoAnalytics.d.ts +45 -69
  5. package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -1
  6. package/dist/cjs/src/FormoAnalytics.js +378 -404
  7. package/dist/cjs/src/FormoAnalytics.js.map +1 -1
  8. package/dist/cjs/src/FormoAnalyticsProvider.d.ts +2 -2
  9. package/dist/cjs/src/FormoAnalyticsProvider.d.ts.map +1 -1
  10. package/dist/cjs/src/FormoAnalyticsProvider.js +120 -29
  11. package/dist/cjs/src/FormoAnalyticsProvider.js.map +1 -1
  12. package/dist/cjs/src/constants/config.d.ts +2 -2
  13. package/dist/cjs/src/constants/config.d.ts.map +1 -1
  14. package/dist/cjs/src/constants/config.js +3 -3
  15. package/dist/cjs/src/constants/config.js.map +1 -1
  16. package/dist/cjs/src/constants/events.d.ts +3 -1
  17. package/dist/cjs/src/constants/events.d.ts.map +1 -1
  18. package/dist/cjs/src/constants/events.js +2 -0
  19. package/dist/cjs/src/constants/events.js.map +1 -1
  20. package/dist/cjs/src/types/base.d.ts +10 -2
  21. package/dist/cjs/src/types/base.d.ts.map +1 -1
  22. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  23. package/dist/esm/src/FormoAnalytics.d.ts +45 -69
  24. package/dist/esm/src/FormoAnalytics.d.ts.map +1 -1
  25. package/dist/esm/src/FormoAnalytics.js +381 -407
  26. package/dist/esm/src/FormoAnalytics.js.map +1 -1
  27. package/dist/esm/src/FormoAnalyticsProvider.d.ts +2 -2
  28. package/dist/esm/src/FormoAnalyticsProvider.d.ts.map +1 -1
  29. package/dist/esm/src/FormoAnalyticsProvider.js +120 -29
  30. package/dist/esm/src/FormoAnalyticsProvider.js.map +1 -1
  31. package/dist/esm/src/constants/config.d.ts +2 -2
  32. package/dist/esm/src/constants/config.d.ts.map +1 -1
  33. package/dist/esm/src/constants/config.js +2 -2
  34. package/dist/esm/src/constants/config.js.map +1 -1
  35. package/dist/esm/src/constants/events.d.ts +3 -1
  36. package/dist/esm/src/constants/events.d.ts.map +1 -1
  37. package/dist/esm/src/constants/events.js +2 -0
  38. package/dist/esm/src/constants/events.js.map +1 -1
  39. package/dist/esm/src/types/base.d.ts +10 -2
  40. package/dist/esm/src/types/base.d.ts.map +1 -1
  41. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  42. package/dist/index.umd.min.js +1 -1
  43. package/dist/index.umd.min.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/FormoAnalytics.ts +293 -448
  46. package/src/FormoAnalyticsProvider.tsx +60 -30
  47. package/src/constants/config.ts +2 -2
  48. package/src/constants/events.ts +2 -0
  49. package/src/types/base.ts +16 -2
  50. package/dist/cjs/src/utils/index.d.ts +0 -2
  51. package/dist/cjs/src/utils/index.d.ts.map +0 -1
  52. package/dist/cjs/src/utils/index.js +0 -18
  53. package/dist/cjs/src/utils/index.js.map +0 -1
  54. package/dist/cjs/src/utils/isNotEmptyObject.d.ts +0 -2
  55. package/dist/cjs/src/utils/isNotEmptyObject.d.ts.map +0 -1
  56. package/dist/cjs/src/utils/isNotEmptyObject.js +0 -9
  57. package/dist/cjs/src/utils/isNotEmptyObject.js.map +0 -1
  58. package/dist/esm/src/utils/index.d.ts +0 -2
  59. package/dist/esm/src/utils/index.d.ts.map +0 -1
  60. package/dist/esm/src/utils/index.js +0 -2
  61. package/dist/esm/src/utils/index.js.map +0 -1
  62. package/dist/esm/src/utils/isNotEmptyObject.d.ts +0 -2
  63. package/dist/esm/src/utils/isNotEmptyObject.d.ts.map +0 -1
  64. package/dist/esm/src/utils/isNotEmptyObject.js +0 -6
  65. package/dist/esm/src/utils/isNotEmptyObject.js.map +0 -1
  66. package/src/utils/index.ts +0 -1
  67. package/src/utils/isNotEmptyObject.ts +0 -5
@@ -45,258 +45,170 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
45
45
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
46
  }
47
47
  };
48
- import axios from 'axios';
49
- import { COUNTRY_LIST, EVENTS_API, SESSION_STORAGE_ID_KEY, Event, } from './constants';
50
- import { isNotEmpty } from './utils';
51
- import { H } from 'highlight.run';
48
+ import axios from "axios";
49
+ import { COUNTRY_LIST, EVENTS_API_URL, Event, } from "./constants";
50
+ import { H } from "highlight.run";
52
51
  var FormoAnalytics = /** @class */ (function () {
53
- function FormoAnalytics(apiKey, projectId) {
52
+ function FormoAnalytics(apiKey, options) {
53
+ if (options === void 0) { options = {}; }
54
54
  var _a;
55
55
  this.apiKey = apiKey;
56
- this.projectId = projectId;
57
- this._registeredProviderListeners = {};
58
- this.sessionKey = 'walletAddress';
59
- this.sessionIdKey = SESSION_STORAGE_ID_KEY;
60
- this.timezoneToCountry = COUNTRY_LIST;
56
+ this.options = options;
57
+ this._providerListeners = {};
61
58
  this.config = {
62
- token: this.apiKey,
59
+ apiKey: apiKey,
63
60
  };
64
- var provider = (window === null || window === void 0 ? void 0 : window.ethereum) || ((_a = window.web3) === null || _a === void 0 ? void 0 : _a.currentProvider);
61
+ var provider = (window === null || window === void 0 ? void 0 : window.ethereum) || ((_a = window.web3) === null || _a === void 0 ? void 0 : _a.currentProvider) || (options === null || options === void 0 ? void 0 : options.provider);
65
62
  if (provider) {
66
63
  this.trackProvider(provider);
67
64
  }
68
65
  }
69
- FormoAnalytics.init = function (apiKey, projectId) {
66
+ FormoAnalytics.init = function (apiKey, options) {
70
67
  return __awaiter(this, void 0, void 0, function () {
71
- var config, instance;
72
68
  return __generator(this, function (_a) {
73
- config = {
74
- token: apiKey,
75
- };
76
- instance = new FormoAnalytics(apiKey, projectId);
77
- instance.config = config;
78
- return [2 /*return*/, instance];
69
+ // May be needed for delayed loading
70
+ // https://github.com/segmentio/analytics-next/tree/master/packages/browser#lazy--delayed-loading
71
+ return [2 /*return*/, new FormoAnalytics(apiKey, options)];
79
72
  });
80
73
  });
81
74
  };
82
- Object.defineProperty(FormoAnalytics.prototype, "provider", {
83
- get: function () {
84
- return this._provider;
85
- },
86
- enumerable: false,
87
- configurable: true
88
- });
89
- FormoAnalytics.prototype.identifyUser = function (userData) {
90
- this.trackEvent(Event.IDENTIFY, userData);
91
- };
92
- FormoAnalytics.prototype.getSessionId = function () {
93
- var existingSessionId = this.getCookieValue(this.sessionIdKey);
94
- if (existingSessionId) {
95
- return existingSessionId;
96
- }
97
- var newSessionId = this.generateSessionId();
98
- return newSessionId;
99
- };
100
- // Function to set the session cookie
101
- FormoAnalytics.prototype.setSessionCookie = function (domain) {
102
- var sessionId = this.getSessionId();
103
- var cookieValue = "".concat(this.sessionIdKey, "=").concat(sessionId, "; Max-Age=1800; path=/; secure");
104
- if (domain) {
105
- cookieValue += "; domain=".concat(domain);
106
- }
107
- document.cookie = cookieValue;
108
- };
109
- // Function to generate a new session ID
110
- FormoAnalytics.prototype.generateSessionId = function () {
111
- return crypto.randomUUID();
112
- };
113
- // Function to get a cookie value by name
114
- FormoAnalytics.prototype.getCookieValue = function (name) {
115
- var cookies = document.cookie.split(';').reduce(function (acc, cookie) {
116
- var _a = cookie.split('='), key = _a[0], value = _a[1];
117
- acc[key.trim()] = value;
118
- return acc;
119
- }, {});
120
- return cookies[name];
75
+ /*
76
+ Public SDK functions
77
+ */
78
+ FormoAnalytics.prototype.connect = function (_a) {
79
+ return __awaiter(this, arguments, void 0, function (_b) {
80
+ var chainId = _b.chainId, address = _b.address;
81
+ return __generator(this, function (_c) {
82
+ switch (_c.label) {
83
+ case 0:
84
+ if (!chainId) {
85
+ throw new Error("FormoAnalytics::connect: chain ID cannot be empty");
86
+ }
87
+ if (!address) {
88
+ throw new Error("FormoAnalytics::connect: address cannot be empty");
89
+ }
90
+ this.currentChainId = chainId;
91
+ this.currentConnectedAddress = address;
92
+ return [4 /*yield*/, this.trackEvent(Event.CONNECT, {
93
+ chain_id: chainId,
94
+ address: address,
95
+ })];
96
+ case 1:
97
+ _c.sent();
98
+ return [2 /*return*/];
99
+ }
100
+ });
101
+ });
121
102
  };
122
- // Function to send tracking data
123
- FormoAnalytics.prototype.trackEvent = function (action, payload) {
103
+ FormoAnalytics.prototype.disconnect = function (params) {
124
104
  return __awaiter(this, void 0, void 0, function () {
125
- var maxRetries, attempt, apiUrl, address, requestData, sendRequest;
126
- var _this = this;
105
+ var address, chainId;
127
106
  return __generator(this, function (_a) {
128
107
  switch (_a.label) {
129
108
  case 0:
130
- maxRetries = 3;
131
- attempt = 0;
132
- this.setSessionCookie(this.config.domain);
133
- apiUrl = this.buildApiUrl();
134
- return [4 /*yield*/, this.getCurrentWallet()];
109
+ address = (params === null || params === void 0 ? void 0 : params.address) || this.currentConnectedAddress;
110
+ chainId = (params === null || params === void 0 ? void 0 : params.chainId) || this.currentChainId;
111
+ return [4 /*yield*/, this.handleDisconnect(chainId, address)];
135
112
  case 1:
136
- address = _a.sent();
137
- requestData = {
138
- project_id: this.projectId,
139
- address: address,
140
- session_id: this.getSessionId(),
141
- timestamp: new Date().toISOString(),
142
- action: action,
143
- version: '1',
144
- payload: isNotEmpty(payload) ? this.maskSensitiveData(payload) : payload,
145
- };
146
- sendRequest = function () { return __awaiter(_this, void 0, void 0, function () {
147
- var response, error_1, retryDelay;
148
- return __generator(this, function (_a) {
149
- switch (_a.label) {
150
- case 0:
151
- _a.trys.push([0, 2, , 3]);
152
- return [4 /*yield*/, axios.post(apiUrl, JSON.stringify(requestData), {
153
- headers: {
154
- 'Content-Type': 'application/json',
155
- },
156
- })];
157
- case 1:
158
- response = _a.sent();
159
- if (response.status >= 200 && response.status < 300) {
160
- console.log('Event sent successfully:', action);
161
- }
162
- else {
163
- throw new Error("Failed with status: ".concat(response.status));
164
- }
165
- return [3 /*break*/, 3];
166
- case 2:
167
- error_1 = _a.sent();
168
- attempt++;
169
- if (attempt <= maxRetries) {
170
- retryDelay = Math.pow(2, attempt) * 1000;
171
- console.error("Attempt ".concat(attempt, ": Retrying event \"").concat(action, "\" in ").concat(retryDelay / 1000, " seconds..."));
172
- setTimeout(sendRequest, retryDelay);
173
- }
174
- else {
175
- H.consumeError(error_1, "Request data: ".concat(JSON.stringify(requestData)));
176
- console.error("Event \"".concat(action, "\" failed after ").concat(maxRetries, " attempts. Error: ").concat(error_1));
177
- }
178
- return [3 /*break*/, 3];
179
- case 3: return [2 /*return*/];
180
- }
181
- });
182
- }); };
183
- return [4 /*yield*/, sendRequest()];
184
- case 2:
185
113
  _a.sent();
186
114
  return [2 /*return*/];
187
115
  }
188
116
  });
189
117
  });
190
118
  };
191
- // Function to mask sensitive data in the payload
192
- FormoAnalytics.prototype.maskSensitiveData = function (data) {
193
- // Check if data is null or undefined
194
- if (data === null || data === undefined) {
195
- console.warn('Data is null or undefined, returning null');
196
- return null;
197
- }
198
- // Check if data is a string; if so, parse it to an object
199
- if (typeof data === 'string') {
200
- var parsedData = void 0;
201
- try {
202
- parsedData = JSON.parse(data);
203
- }
204
- catch (error) {
205
- console.error('Failed to parse JSON:', error);
206
- return null; // Return null if parsing fails
207
- }
208
- var sensitiveFields = [
209
- 'username',
210
- 'user',
211
- 'user_id',
212
- 'password',
213
- 'email',
214
- 'phone',
215
- ];
216
- // Create a new object to store masked data
217
- var maskedData_1 = __assign({}, parsedData);
218
- // Mask sensitive fields
219
- sensitiveFields.forEach(function (field) {
220
- if (field in maskedData_1) {
221
- maskedData_1[field] = '********'; // Replace value with masked string
119
+ FormoAnalytics.prototype.chain = function (_a) {
120
+ return __awaiter(this, arguments, void 0, function (_b) {
121
+ var chainId = _b.chainId, address = _b.address;
122
+ return __generator(this, function (_c) {
123
+ switch (_c.label) {
124
+ case 0:
125
+ if (!chainId || Number(chainId) === 0) {
126
+ throw new Error("FormoAnalytics::chain: chainId cannot be empty or 0");
127
+ }
128
+ if (!address && !this.currentConnectedAddress) {
129
+ throw new Error("FormoAnalytics::chain: address was empty and no previous address has been recorded. You can either pass an address or call connect() first");
130
+ }
131
+ if (isNaN(Number(chainId))) {
132
+ throw new Error("FormoAnalytics::chain: chainId must be a valid decimal number");
133
+ }
134
+ this.currentChainId = chainId;
135
+ return [4 /*yield*/, this.trackEvent(Event.CHAIN_CHANGED, {
136
+ chain_id: chainId,
137
+ address: address || this.currentConnectedAddress,
138
+ })];
139
+ case 1:
140
+ _c.sent();
141
+ return [2 /*return*/];
222
142
  }
223
143
  });
224
- return maskedData_1; // Return the new object with masked fields
225
- }
226
- else if (typeof data === 'object') {
227
- // If data is already an object, handle masking directly
228
- var sensitiveFields = [
229
- 'username',
230
- 'user',
231
- 'user_id',
232
- 'password',
233
- 'email',
234
- 'phone',
235
- ];
236
- var maskedData_2 = __assign({}, data);
237
- // Mask sensitive fields
238
- sensitiveFields.forEach(function (field) {
239
- if (field in maskedData_2) {
240
- maskedData_2[field] = '********'; // Replace value with masked string
144
+ });
145
+ };
146
+ // TODO: allow custom url as input
147
+ FormoAnalytics.prototype.page = function () {
148
+ return __awaiter(this, void 0, void 0, function () {
149
+ return __generator(this, function (_a) {
150
+ switch (_a.label) {
151
+ case 0: return [4 /*yield*/, this.trackPageHit()];
152
+ case 1:
153
+ _a.sent();
154
+ return [2 /*return*/];
241
155
  }
242
156
  });
243
- return maskedData_2; // Return the new object with masked fields
244
- }
245
- return data;
157
+ });
246
158
  };
247
- // Function to track page hits
248
- FormoAnalytics.prototype.trackPageHit = function () {
249
- var _this = this;
250
- if (window.__nightmare || window.navigator.webdriver || window.Cypress)
251
- return;
252
- var location;
253
- var language;
254
- try {
255
- var timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
256
- location = this.timezoneToCountry[timezone];
257
- language =
258
- navigator.languages && navigator.languages.length
259
- ? navigator.languages[0]
260
- : navigator.language || 'en';
261
- }
262
- catch (error) {
263
- console.error('Error resolving timezone or language:', error);
264
- }
265
- setTimeout(function () {
266
- var url = new URL(window.location.href);
267
- var params = new URLSearchParams(url.search);
268
- _this.trackEvent(Event.PAGE, {
269
- 'user-agent': window.navigator.userAgent,
270
- locale: language,
271
- location: location,
272
- referrer: document.referrer,
273
- pathname: window.location.pathname,
274
- href: window.location.href,
275
- utm_source: params.get('utm_source'),
276
- utm_medium: params.get('utm_medium'),
277
- utm_campaign: params.get('utm_campaign'),
278
- ref: params.get('ref'),
159
+ FormoAnalytics.prototype.track = function (eventName, eventData) {
160
+ return __awaiter(this, void 0, void 0, function () {
161
+ return __generator(this, function (_a) {
162
+ switch (_a.label) {
163
+ case 0: return [4 /*yield*/, this.trackEvent(eventName, eventData)];
164
+ case 1:
165
+ _a.sent();
166
+ return [2 /*return*/];
167
+ }
279
168
  });
280
- }, 300);
169
+ });
281
170
  };
171
+ /*
172
+ SDK tracking and event listener functions
173
+ */
282
174
  FormoAnalytics.prototype.trackProvider = function (provider) {
283
175
  if (provider === this._provider) {
176
+ console.log("Provider already tracked.");
284
177
  return;
285
178
  }
286
179
  this.currentChainId = undefined;
287
- this.currentConnectedAccount = undefined;
180
+ this.currentConnectedAddress = undefined;
288
181
  if (this._provider) {
289
- var eventNames = Object.keys(this._registeredProviderListeners);
182
+ var eventNames = Object.keys(this._providerListeners);
290
183
  for (var _i = 0, eventNames_1 = eventNames; _i < eventNames_1.length; _i++) {
291
184
  var eventName = eventNames_1[_i];
292
- this._provider.removeListener(eventName, this._registeredProviderListeners[eventName]);
293
- delete this._registeredProviderListeners[eventName];
185
+ this._provider.removeListener(eventName, this._providerListeners[eventName]);
186
+ delete this._providerListeners[eventName];
294
187
  }
295
188
  }
189
+ console.log("Tracking new provider:", provider);
296
190
  this._provider = provider;
297
- this.getCurrentWallet();
298
- this.registerAccountsChangedListener();
191
+ this.getAddress();
192
+ this.registerAddressChangedListener();
299
193
  this.registerChainChangedListener();
194
+ // TODO: track signing and transactions
195
+ // https://linear.app/getformo/issue/P-607/sdk-support-signature-and-transaction-events
196
+ };
197
+ FormoAnalytics.prototype.registerAddressChangedListener = function () {
198
+ var _this = this;
199
+ var _a, _b;
200
+ var listener = function () {
201
+ var args = [];
202
+ for (var _i = 0; _i < arguments.length; _i++) {
203
+ args[_i] = arguments[_i];
204
+ }
205
+ return _this.onAddressChanged(args[0]);
206
+ };
207
+ (_a = this._provider) === null || _a === void 0 ? void 0 : _a.on("accountsChanged", listener);
208
+ this._providerListeners["accountsChanged"] = listener;
209
+ var onAddressDisconnected = this.onAddressDisconnected.bind(this);
210
+ (_b = this._provider) === null || _b === void 0 ? void 0 : _b.on("disconnect", onAddressDisconnected);
211
+ this._providerListeners["disconnect"] = onAddressDisconnected;
300
212
  };
301
213
  FormoAnalytics.prototype.registerChainChangedListener = function () {
302
214
  var _this = this;
@@ -308,264 +220,326 @@ var FormoAnalytics = /** @class */ (function () {
308
220
  }
309
221
  return _this.onChainChanged(args[0]);
310
222
  };
311
- (_a = this.provider) === null || _a === void 0 ? void 0 : _a.on('chainChanged', listener);
312
- this._registeredProviderListeners['chainChanged'] = listener;
223
+ (_a = this.provider) === null || _a === void 0 ? void 0 : _a.on("chainChanged", listener);
224
+ this._providerListeners["chainChanged"] = listener;
313
225
  };
314
- FormoAnalytics.prototype.handleAccountDisconnected = function () {
315
- if (!this.currentConnectedAccount) {
316
- return;
317
- }
318
- var disconnectAttributes = {
319
- account: this.currentConnectedAccount,
320
- chainId: this.currentChainId,
321
- };
322
- this.currentChainId = undefined;
323
- this.currentConnectedAccount = undefined;
324
- this.clearWalletAddress();
325
- return this.trackEvent(Event.DISCONNECT, disconnectAttributes);
226
+ FormoAnalytics.prototype.onAddressChanged = function (addresses) {
227
+ return __awaiter(this, void 0, void 0, function () {
228
+ return __generator(this, function (_a) {
229
+ if (addresses.length > 0) {
230
+ this.onAddressConnected(addresses[0]);
231
+ }
232
+ else {
233
+ this.onAddressDisconnected();
234
+ }
235
+ return [2 /*return*/];
236
+ });
237
+ });
238
+ };
239
+ FormoAnalytics.prototype.onAddressConnected = function (address) {
240
+ return __awaiter(this, void 0, void 0, function () {
241
+ var _a;
242
+ return __generator(this, function (_b) {
243
+ switch (_b.label) {
244
+ case 0:
245
+ if (address === this.currentConnectedAddress) {
246
+ // We have already reported this address
247
+ return [2 /*return*/];
248
+ }
249
+ else {
250
+ this.currentConnectedAddress = address;
251
+ }
252
+ _a = this;
253
+ return [4 /*yield*/, this.getCurrentChainId()];
254
+ case 1:
255
+ _a.currentChainId = _b.sent();
256
+ this.connect({ chainId: this.currentChainId, address: address });
257
+ return [2 /*return*/];
258
+ }
259
+ });
260
+ });
261
+ };
262
+ FormoAnalytics.prototype.handleDisconnect = function (chainId, address) {
263
+ return __awaiter(this, void 0, void 0, function () {
264
+ var payload;
265
+ return __generator(this, function (_a) {
266
+ switch (_a.label) {
267
+ case 0:
268
+ payload = {
269
+ chain_id: chainId || this.currentChainId,
270
+ address: address || this.currentConnectedAddress,
271
+ };
272
+ this.currentChainId = undefined;
273
+ this.currentConnectedAddress = undefined;
274
+ return [4 /*yield*/, this.trackEvent(Event.DISCONNECT, payload)];
275
+ case 1:
276
+ _a.sent();
277
+ return [2 /*return*/];
278
+ }
279
+ });
280
+ });
281
+ };
282
+ FormoAnalytics.prototype.onAddressDisconnected = function () {
283
+ return __awaiter(this, void 0, void 0, function () {
284
+ return __generator(this, function (_a) {
285
+ switch (_a.label) {
286
+ case 0: return [4 /*yield*/, this.handleDisconnect(this.currentChainId, this.currentConnectedAddress)];
287
+ case 1:
288
+ _a.sent();
289
+ return [2 /*return*/];
290
+ }
291
+ });
292
+ });
326
293
  };
327
294
  FormoAnalytics.prototype.onChainChanged = function (chainIdHex) {
328
295
  return __awaiter(this, void 0, void 0, function () {
329
- var res, err_1;
296
+ var address;
330
297
  return __generator(this, function (_a) {
331
298
  switch (_a.label) {
332
299
  case 0:
333
- this.currentChainId = parseInt(chainIdHex).toString();
334
- if (!!this.currentConnectedAccount) return [3 /*break*/, 4];
300
+ this.currentChainId = parseInt(chainIdHex);
301
+ if (!!this.currentConnectedAddress) return [3 /*break*/, 2];
335
302
  if (!this.provider) {
336
- console.error('error', 'FormoAnalytics::onChainChanged: provider not found. CHAIN_CHANGED not reported');
337
- return [2 /*return*/];
303
+ console.log("FormoAnalytics::onChainChanged: provider not found. CHAIN_CHANGED not reported");
304
+ return [2 /*return*/, Promise.resolve()];
338
305
  }
339
- _a.label = 1;
306
+ return [4 /*yield*/, this.getAddress()];
340
307
  case 1:
341
- _a.trys.push([1, 3, , 4]);
342
- return [4 /*yield*/, this.provider.request({
343
- method: 'eth_accounts',
344
- })];
308
+ address = _a.sent();
309
+ if (!address) {
310
+ console.log("FormoAnalytics::onChainChanged: Unable to fetch or store connected address");
311
+ return [2 /*return*/, Promise.resolve()];
312
+ }
313
+ this.currentConnectedAddress = address[0];
314
+ _a.label = 2;
345
315
  case 2:
346
- res = _a.sent();
347
- if (!res || res.length === 0) {
348
- console.error('error', 'FormoAnalytics::onChainChanged: unable to get account. eth_accounts returned empty');
349
- return [2 /*return*/];
316
+ // Proceed only if the address exists
317
+ if (this.currentConnectedAddress) {
318
+ return [2 /*return*/, this.chain({
319
+ chainId: this.currentChainId,
320
+ address: this.currentConnectedAddress,
321
+ })];
350
322
  }
351
- this.currentConnectedAccount = res[0];
352
- return [3 /*break*/, 4];
353
- case 3:
354
- err_1 = _a.sent();
355
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
356
- if (err_1.code !== 4001) {
357
- // 4001: The request is rejected by the user , see https://docs.metamask.io/wallet/reference/provider-api/#errors
358
- console.error('error', "FormoAnalytics::onChainChanged: unable to get account. eth_accounts threw an error", err_1);
359
- return [2 /*return*/];
323
+ else {
324
+ console.log("FormoAnalytics::onChainChanged: currentConnectedAddress is null despite fetch attempt");
360
325
  }
361
- return [3 /*break*/, 4];
362
- case 4: return [2 /*return*/, this.chain({
363
- chainId: this.currentChainId,
364
- account: this.currentConnectedAccount,
365
- })];
326
+ return [2 /*return*/];
366
327
  }
367
328
  });
368
329
  });
369
330
  };
370
- FormoAnalytics.prototype.onAccountsChanged = function (accounts) {
371
- return __awaiter(this, void 0, void 0, function () {
372
- var newAccount;
331
+ // TOFIX: support multiple page hit events
332
+ // TODO: Add event listener and support for SPA and hash-based navigation
333
+ // https://linear.app/getformo/issue/P-800/sdk-support-spa-and-hash-based-routing
334
+ FormoAnalytics.prototype.trackPageHit = function () {
335
+ var _this = this;
336
+ var pathname = window.location.pathname;
337
+ var href = window.location.href;
338
+ setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
373
339
  return __generator(this, function (_a) {
374
- if (accounts.length > 0) {
375
- newAccount = accounts[0];
376
- if (newAccount !== this.currentConnectedAccount) {
377
- this.handleAccountConnected(newAccount);
378
- }
379
- }
380
- else {
381
- this.handleAccountDisconnected();
382
- }
340
+ this.trackEvent(Event.PAGE, {
341
+ pathname: pathname,
342
+ href: href,
343
+ });
383
344
  return [2 /*return*/];
384
345
  });
385
- });
386
- };
387
- FormoAnalytics.prototype.registerAccountsChangedListener = function () {
388
- var _this = this;
389
- var _a, _b;
390
- var listener = function () {
391
- var args = [];
392
- for (var _i = 0; _i < arguments.length; _i++) {
393
- args[_i] = arguments[_i];
394
- }
395
- return _this.onAccountsChanged(args[0]);
396
- };
397
- (_a = this._provider) === null || _a === void 0 ? void 0 : _a.on('accountsChanged', listener);
398
- this._registeredProviderListeners['accountsChanged'] = listener;
399
- var handleAccountDisconnected = this.handleAccountDisconnected.bind(this);
400
- (_b = this._provider) === null || _b === void 0 ? void 0 : _b.on('disconnect', handleAccountDisconnected);
401
- this._registeredProviderListeners['disconnect'] = handleAccountDisconnected;
346
+ }); }, 300);
402
347
  };
403
- FormoAnalytics.prototype.getCurrentChainId = function () {
348
+ // TODO: refactor this with event queue and flushing
349
+ // https://linear.app/getformo/issue/P-835/sdk-refactor-retries-with-event-queue-and-batching
350
+ FormoAnalytics.prototype.trackEvent = function (action, payload) {
404
351
  return __awaiter(this, void 0, void 0, function () {
405
- var chainIdHex;
352
+ var address, requestData, response, error_1;
406
353
  var _a;
407
354
  return __generator(this, function (_b) {
408
355
  switch (_b.label) {
356
+ case 0: return [4 /*yield*/, this.getAddress()];
357
+ case 1:
358
+ address = _b.sent();
359
+ _a = {
360
+ address: address,
361
+ timestamp: new Date().toISOString(),
362
+ action: action,
363
+ version: "1"
364
+ };
365
+ return [4 /*yield*/, this.buildEventPayload(payload)];
366
+ case 2:
367
+ requestData = (_a.payload = _b.sent(),
368
+ _a);
369
+ _b.label = 3;
370
+ case 3:
371
+ _b.trys.push([3, 5, , 6]);
372
+ return [4 /*yield*/, axios.post(EVENTS_API_URL, JSON.stringify(requestData), {
373
+ headers: {
374
+ "Content-Type": "application/json",
375
+ Authorization: "Bearer ".concat(this.config.apiKey),
376
+ },
377
+ })];
378
+ case 4:
379
+ response = _b.sent();
380
+ if (response.status >= 200 && response.status < 300) {
381
+ console.log("Event sent successfully:", action);
382
+ }
383
+ else {
384
+ throw new Error("Failed with status: ".concat(response.status));
385
+ }
386
+ return [3 /*break*/, 6];
387
+ case 5:
388
+ error_1 = _b.sent();
389
+ H.consumeError(error_1, "Request data: ".concat(JSON.stringify(requestData)));
390
+ console.error("Event \"".concat(action, "\" failed. Error: ").concat(error_1));
391
+ return [3 /*break*/, 6];
392
+ case 6: return [2 /*return*/];
393
+ }
394
+ });
395
+ });
396
+ };
397
+ Object.defineProperty(FormoAnalytics.prototype, "provider", {
398
+ /*
399
+ Utility functions
400
+ */
401
+ get: function () {
402
+ return this._provider;
403
+ },
404
+ enumerable: false,
405
+ configurable: true
406
+ });
407
+ FormoAnalytics.prototype.getAddress = function () {
408
+ return __awaiter(this, void 0, void 0, function () {
409
+ var accounts, address, err_1;
410
+ return __generator(this, function (_a) {
411
+ switch (_a.label) {
409
412
  case 0:
410
413
  if (!this.provider) {
411
- console.error('FormoAnalytics::getCurrentChainId: provider not set');
414
+ console.log("FormoAnalytics::getAddress: the provider is not set");
415
+ return [2 /*return*/, null];
412
416
  }
413
- return [4 /*yield*/, ((_a = this.provider) === null || _a === void 0 ? void 0 : _a.request({
414
- method: 'eth_chainId',
415
- }))];
417
+ _a.label = 1;
416
418
  case 1:
417
- chainIdHex = _b.sent();
418
- // Because we're connected, the chainId cannot be null
419
- if (!chainIdHex) {
420
- console.error("FormoAnalytics::getCurrentChainId: chainIdHex is: ".concat(chainIdHex));
419
+ _a.trys.push([1, 3, , 4]);
420
+ return [4 /*yield*/, this.getAccounts()];
421
+ case 2:
422
+ accounts = _a.sent();
423
+ if (accounts && accounts.length > 0) {
424
+ address = accounts[0];
425
+ // TODO: how to handle multiple addresses? Should we emit a connect event here? Since the user has not manually connected
426
+ // https://linear.app/getformo/issue/P-691/sdk-detect-multiple-wallets-using-eip6963
427
+ this.onAddressConnected(address);
428
+ return [2 /*return*/, address];
421
429
  }
422
- return [2 /*return*/, parseInt(chainIdHex, 16).toString()];
430
+ return [3 /*break*/, 4];
431
+ case 3:
432
+ err_1 = _a.sent();
433
+ console.log("Failed to fetch accounts from provider:", err_1);
434
+ return [2 /*return*/, null];
435
+ case 4: return [2 /*return*/, null];
423
436
  }
424
437
  });
425
438
  });
426
439
  };
427
- FormoAnalytics.prototype.handleAccountConnected = function (account) {
440
+ FormoAnalytics.prototype.getAccounts = function () {
428
441
  return __awaiter(this, void 0, void 0, function () {
442
+ var res, err_2;
429
443
  var _a;
430
444
  return __generator(this, function (_b) {
431
445
  switch (_b.label) {
432
446
  case 0:
433
- if (account === this.currentConnectedAccount) {
434
- // We have already reported this account
435
- return [2 /*return*/];
447
+ _b.trys.push([0, 2, , 3]);
448
+ return [4 /*yield*/, ((_a = this.provider) === null || _a === void 0 ? void 0 : _a.request({
449
+ method: "eth_accounts",
450
+ }))];
451
+ case 1:
452
+ res = _b.sent();
453
+ if (!res || res.length === 0) {
454
+ console.log("FormoAnalytics::getAccounts: unable to get account. eth_accounts returned empty");
455
+ return [2 /*return*/, null];
436
456
  }
437
- else {
438
- this.currentConnectedAccount = account;
457
+ return [2 /*return*/, res];
458
+ case 2:
459
+ err_2 = _b.sent();
460
+ if (err_2.code !== 4001) {
461
+ console.log("FormoAnalytics::getAccounts: eth_accounts threw an error", err_2);
439
462
  }
440
- _a = this;
441
- return [4 /*yield*/, this.getCurrentChainId()];
442
- case 1:
443
- _a.currentChainId = _b.sent();
444
- this.connect({ account: account, chainId: this.currentChainId });
445
- this.storeWalletAddress(account);
446
- return [2 /*return*/];
463
+ return [2 /*return*/, null];
464
+ case 3: return [2 /*return*/];
447
465
  }
448
466
  });
449
467
  });
450
468
  };
451
- FormoAnalytics.prototype.getCurrentWallet = function () {
469
+ FormoAnalytics.prototype.getCurrentChainId = function () {
452
470
  return __awaiter(this, void 0, void 0, function () {
453
- var sessionData, parsedData, sessionExpiry, currentTime;
454
- return __generator(this, function (_a) {
455
- if (!this.provider) {
456
- console.warn('FormoAnalytics::getCurrentWallet: the provider is not set');
457
- return [2 /*return*/];
458
- }
459
- sessionData = sessionStorage.getItem(this.sessionKey);
460
- if (!sessionData) {
461
- return [2 /*return*/, null];
462
- }
463
- parsedData = JSON.parse(sessionData);
464
- sessionExpiry = 30 * 60 * 1000;
465
- currentTime = Date.now();
466
- if (currentTime - parsedData.timestamp > sessionExpiry) {
467
- console.warn('Session expired. Ignoring wallet address.');
468
- sessionStorage.removeItem(this.sessionKey); // Clear expired session data
469
- return [2 /*return*/, ''];
471
+ var chainIdHex, err_3;
472
+ var _a;
473
+ return __generator(this, function (_b) {
474
+ switch (_b.label) {
475
+ case 0:
476
+ if (!this.provider) {
477
+ console.error("FormoAnalytics::getCurrentChainId: provider not set");
478
+ }
479
+ _b.label = 1;
480
+ case 1:
481
+ _b.trys.push([1, 3, , 4]);
482
+ return [4 /*yield*/, ((_a = this.provider) === null || _a === void 0 ? void 0 : _a.request({
483
+ method: "eth_chainId",
484
+ }))];
485
+ case 2:
486
+ chainIdHex = _b.sent();
487
+ if (!chainIdHex) {
488
+ console.log("FormoAnalytics::fetchChainId: chain id not found");
489
+ return [2 /*return*/, 0];
490
+ }
491
+ return [2 /*return*/, parseInt(chainIdHex, 16)];
492
+ case 3:
493
+ err_3 = _b.sent();
494
+ console.log("FormoAnalytics::fetchChainId: eth_chainId threw an error", err_3);
495
+ return [2 /*return*/, 0];
496
+ case 4: return [2 /*return*/];
470
497
  }
471
- this.handleAccountConnected(parsedData.address);
472
- return [2 /*return*/, parsedData.address || ''];
473
498
  });
474
499
  });
475
500
  };
476
- /**
477
- * Stores the wallet address in session storage when connected.
478
- * @param address - The wallet address to store.
479
- */
480
- FormoAnalytics.prototype.storeWalletAddress = function (address) {
481
- if (!address) {
482
- console.error('No wallet address provided to store.');
483
- return;
501
+ FormoAnalytics.prototype.getLocation = function () {
502
+ try {
503
+ var timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
504
+ return COUNTRY_LIST[timezone];
484
505
  }
485
- var sessionData = {
486
- address: address,
487
- timestamp: Date.now(),
488
- };
489
- sessionStorage.setItem(this.sessionKey, JSON.stringify(sessionData));
490
- };
491
- /**
492
- * Clears the wallet address from session storage when disconnected.
493
- */
494
- FormoAnalytics.prototype.clearWalletAddress = function () {
495
- sessionStorage.removeItem(this.sessionKey);
496
- };
497
- // Function to build the API URL
498
- FormoAnalytics.prototype.buildApiUrl = function () {
499
- var _a = this.config, host = _a.host, proxy = _a.proxy, token = _a.token, _b = _a.dataSource, dataSource = _b === void 0 ? 'analytics_events' : _b;
500
- if (token) {
501
- if (proxy) {
502
- return "".concat(proxy, "/api/tracking");
503
- }
504
- if (host) {
505
- return "".concat(host.replace(/\/+$/, ''), "/v0/events?name=").concat(dataSource, "&token=").concat(token);
506
- }
507
- return "".concat(EVENTS_API, "?name=").concat(dataSource, "&token=").concat(token);
506
+ catch (error) {
507
+ console.error("Error resolving timezone:", error);
508
+ return undefined;
508
509
  }
509
- return 'Error: No token provided';
510
510
  };
511
- FormoAnalytics.prototype.connect = function (_a) {
512
- var account = _a.account, chainId = _a.chainId;
513
- if (!chainId) {
514
- throw new Error('FormoAnalytics::connect: chainId cannot be empty');
515
- }
516
- if (!account) {
517
- throw new Error('FormoAnalytics::connect: account cannot be empty');
511
+ FormoAnalytics.prototype.getLanguage = function () {
512
+ try {
513
+ return ((navigator.languages && navigator.languages.length
514
+ ? navigator.languages[0]
515
+ : navigator.language) || "en");
518
516
  }
519
- this.currentChainId = chainId.toString();
520
- this.currentConnectedAccount = account;
521
- return this.trackEvent(Event.CONNECT, {
522
- chainId: chainId,
523
- address: account,
524
- });
525
- };
526
- FormoAnalytics.prototype.disconnect = function (attributes) {
527
- var account = (attributes === null || attributes === void 0 ? void 0 : attributes.account) || this.currentConnectedAccount;
528
- if (!account) {
529
- // We have most likely already reported this disconnection with the automatic
530
- // `disconnect` detection
531
- return;
517
+ catch (error) {
518
+ console.error("Error resolving language:", error);
519
+ return "en";
532
520
  }
533
- var chainId = (attributes === null || attributes === void 0 ? void 0 : attributes.chainId) || this.currentChainId;
534
- var eventAttributes = __assign({ account: account }, (chainId && { chainId: chainId }));
535
- this.currentChainId = undefined;
536
- this.currentConnectedAccount = undefined;
537
- return this.trackEvent(Event.DISCONNECT, eventAttributes);
538
521
  };
539
- FormoAnalytics.prototype.chain = function (_a) {
540
- var chainId = _a.chainId, account = _a.account;
541
- if (!chainId || Number(chainId) === 0) {
542
- throw new Error('FormoAnalytics::chain: chainId cannot be empty or 0');
543
- }
544
- if (!account && !this.currentConnectedAccount) {
545
- throw new Error('FormoAnalytics::chain: account was empty and no previous account has been recorded. You can either pass an account or call connect() first');
546
- }
547
- if (isNaN(Number(chainId))) {
548
- throw new Error('FormoAnalytics::chain: chainId must be a valid hex or decimal number');
549
- }
550
- this.currentChainId = chainId.toString();
551
- return this.trackEvent(Event.CHAIN_CHANGED, {
552
- chainId: chainId,
553
- account: account || this.currentConnectedAccount,
522
+ // Adds browser properties to the user-supplied payload
523
+ FormoAnalytics.prototype.buildEventPayload = function () {
524
+ return __awaiter(this, arguments, void 0, function (eventSpecificPayload) {
525
+ var url, params, location, language, address;
526
+ if (eventSpecificPayload === void 0) { eventSpecificPayload = {}; }
527
+ return __generator(this, function (_a) {
528
+ switch (_a.label) {
529
+ case 0:
530
+ url = new URL(window.location.href);
531
+ params = new URLSearchParams(url.search);
532
+ location = this.getLocation();
533
+ language = this.getLanguage();
534
+ return [4 /*yield*/, this.getAddress()];
535
+ case 1:
536
+ address = _a.sent();
537
+ // common browser properties
538
+ return [2 /*return*/, __assign({ "user-agent": window.navigator.userAgent, address: address, locale: language, location: location, referrer: document.referrer, utm_source: params.get("utm_source"), utm_medium: params.get("utm_medium"), utm_campaign: params.get("utm_campaign"), ref: params.get("ref") }, eventSpecificPayload)];
539
+ }
540
+ });
554
541
  });
555
542
  };
556
- FormoAnalytics.prototype.init = function (apiKey, projectId) {
557
- var instance = new FormoAnalytics(apiKey, projectId);
558
- return Promise.resolve(instance);
559
- };
560
- FormoAnalytics.prototype.identify = function (userData) {
561
- this.identifyUser(userData);
562
- };
563
- FormoAnalytics.prototype.page = function () {
564
- this.trackPageHit();
565
- };
566
- FormoAnalytics.prototype.track = function (eventName, eventData) {
567
- this.trackEvent(eventName, eventData);
568
- };
569
543
  return FormoAnalytics;
570
544
  }());
571
545
  export { FormoAnalytics };