@formo/analytics 1.16.19 → 1.17.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 (112) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -1
  2. package/dist/cjs/src/FormoAnalytics.js +1 -0
  3. package/dist/cjs/src/FormoAnalytics.js.map +1 -1
  4. package/dist/cjs/src/FormoAnalyticsProvider.d.ts +1 -1
  5. package/dist/cjs/src/FormoAnalyticsProvider.d.ts.map +1 -1
  6. package/dist/cjs/src/constants/config.d.ts.map +1 -1
  7. package/dist/cjs/src/lib/event/utils.d.ts.map +1 -1
  8. package/dist/cjs/src/lib/ramda/internal/_curry1.d.ts.map +1 -1
  9. package/dist/cjs/src/lib/ramda/internal/_curry2.d.ts.map +1 -1
  10. package/dist/cjs/src/lib/ramda/internal/_curry3.d.ts.map +1 -1
  11. package/dist/cjs/src/lib/version.d.ts +1 -1
  12. package/dist/cjs/src/lib/version.js +1 -1
  13. package/dist/cjs/src/utils/address.d.ts.map +1 -1
  14. package/dist/cjs/src/utils/base.d.ts.map +1 -1
  15. package/dist/cjs/src/utils/converter.d.ts.map +1 -1
  16. package/dist/cjs/src/utils/timestamp.d.ts.map +1 -1
  17. package/dist/cjs/src/validators/address.d.ts.map +1 -1
  18. package/dist/cjs/src/validators/checks.d.ts.map +1 -1
  19. package/dist/cjs/src/validators/object.d.ts.map +1 -1
  20. package/dist/cjs/src/validators/string.d.ts.map +1 -1
  21. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  22. package/dist/esm/src/FormoAnalytics.d.ts.map +1 -1
  23. package/dist/esm/src/FormoAnalytics.js +2 -1
  24. package/dist/esm/src/FormoAnalytics.js.map +1 -1
  25. package/dist/esm/src/FormoAnalyticsProvider.d.ts +1 -1
  26. package/dist/esm/src/FormoAnalyticsProvider.d.ts.map +1 -1
  27. package/dist/esm/src/constants/config.d.ts.map +1 -1
  28. package/dist/esm/src/lib/event/utils.d.ts.map +1 -1
  29. package/dist/esm/src/lib/ramda/internal/_curry1.d.ts.map +1 -1
  30. package/dist/esm/src/lib/ramda/internal/_curry2.d.ts.map +1 -1
  31. package/dist/esm/src/lib/ramda/internal/_curry3.d.ts.map +1 -1
  32. package/dist/esm/src/lib/version.d.ts +1 -1
  33. package/dist/esm/src/lib/version.js +1 -1
  34. package/dist/esm/src/utils/address.d.ts.map +1 -1
  35. package/dist/esm/src/utils/base.d.ts.map +1 -1
  36. package/dist/esm/src/utils/converter.d.ts.map +1 -1
  37. package/dist/esm/src/utils/timestamp.d.ts.map +1 -1
  38. package/dist/esm/src/validators/address.d.ts.map +1 -1
  39. package/dist/esm/src/validators/checks.d.ts.map +1 -1
  40. package/dist/esm/src/validators/object.d.ts.map +1 -1
  41. package/dist/esm/src/validators/string.d.ts.map +1 -1
  42. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  43. package/dist/index.umd.min.js +1 -1
  44. package/dist/index.umd.min.js.map +1 -1
  45. package/package.json +48 -41
  46. package/.env.example +0 -1
  47. package/.github/workflows/ci.yml +0 -51
  48. package/CONTRIBUTING.md +0 -93
  49. package/src/FormoAnalytics.ts +0 -1021
  50. package/src/FormoAnalyticsProvider.tsx +0 -84
  51. package/src/constants/base.ts +0 -6
  52. package/src/constants/config.ts +0 -660
  53. package/src/constants/events.ts +0 -21
  54. package/src/constants/index.ts +0 -3
  55. package/src/global.d.ts +0 -12
  56. package/src/index.ts +0 -3
  57. package/src/lib/event/EventFactory.ts +0 -519
  58. package/src/lib/event/EventManager.ts +0 -39
  59. package/src/lib/event/constants.ts +0 -4
  60. package/src/lib/event/index.ts +0 -3
  61. package/src/lib/event/type.ts +0 -9
  62. package/src/lib/event/utils.ts +0 -33
  63. package/src/lib/fetch.ts +0 -3
  64. package/src/lib/index.ts +0 -5
  65. package/src/lib/logger/Logger.ts +0 -115
  66. package/src/lib/logger/index.ts +0 -2
  67. package/src/lib/logger/type.ts +0 -14
  68. package/src/lib/queue/EventQueue.ts +0 -306
  69. package/src/lib/queue/index.ts +0 -2
  70. package/src/lib/queue/type.ts +0 -6
  71. package/src/lib/ramda/internal/_curry1.ts +0 -19
  72. package/src/lib/ramda/internal/_curry2.ts +0 -37
  73. package/src/lib/ramda/internal/_curry3.ts +0 -68
  74. package/src/lib/ramda/internal/_has.ts +0 -3
  75. package/src/lib/ramda/internal/_isObject.ts +0 -3
  76. package/src/lib/ramda/internal/_isPlaceholder.ts +0 -5
  77. package/src/lib/ramda/mergeDeepRight.ts +0 -13
  78. package/src/lib/ramda/mergeDeepWithKey.ts +0 -22
  79. package/src/lib/ramda/mergeWithKey.ts +0 -28
  80. package/src/lib/storage/StorageManager.ts +0 -51
  81. package/src/lib/storage/built-in/blueprint.ts +0 -17
  82. package/src/lib/storage/built-in/cookie.ts +0 -60
  83. package/src/lib/storage/built-in/memory.ts +0 -23
  84. package/src/lib/storage/built-in/web.ts +0 -57
  85. package/src/lib/storage/constant.ts +0 -2
  86. package/src/lib/storage/index.ts +0 -25
  87. package/src/lib/storage/type.ts +0 -21
  88. package/src/lib/version.ts +0 -2
  89. package/src/types/base.ts +0 -120
  90. package/src/types/events.ts +0 -126
  91. package/src/types/index.ts +0 -3
  92. package/src/types/provider.ts +0 -17
  93. package/src/utils/address.ts +0 -43
  94. package/src/utils/base.ts +0 -3
  95. package/src/utils/converter.ts +0 -44
  96. package/src/utils/generate.ts +0 -16
  97. package/src/utils/index.ts +0 -4
  98. package/src/utils/timestamp.ts +0 -9
  99. package/src/validators/address.ts +0 -69
  100. package/src/validators/agent.ts +0 -4
  101. package/src/validators/checks.ts +0 -160
  102. package/src/validators/index.ts +0 -7
  103. package/src/validators/network.ts +0 -34
  104. package/src/validators/object.ts +0 -4
  105. package/src/validators/string.ts +0 -4
  106. package/src/validators/uint8array.ts +0 -17
  107. package/test/lib/events.spec.ts +0 -12
  108. package/test/utils/address.spec.ts +0 -14
  109. package/test/utils/converter.spec.ts +0 -31
  110. package/test/validators/address.spec.ts +0 -15
  111. package/tsconfig.json +0 -28
  112. package/webpack.config.ts +0 -23
@@ -1,1021 +0,0 @@
1
- import { createStore, EIP6963ProviderDetail } from "mipd";
2
- import {
3
- EVENTS_API_URL,
4
- EventType,
5
- LOCAL_ANONYMOUS_ID_KEY,
6
- SESSION_CURRENT_URL_KEY,
7
- SESSION_USER_ID_KEY,
8
- SESSION_WALLET_DETECTED_KEY,
9
- TEventType,
10
- } from "./constants";
11
- import {
12
- cookie,
13
- EventManager,
14
- EventQueue,
15
- IEventManager,
16
- logger,
17
- Logger,
18
- } from "./lib";
19
- import {
20
- Address,
21
- ChainID,
22
- Config,
23
- EIP1193Provider,
24
- IFormoAnalytics,
25
- IFormoEventContext,
26
- IFormoEventProperties,
27
- Options,
28
- RequestArguments,
29
- RPCError,
30
- SignatureStatus,
31
- TransactionStatus,
32
- } from "./types";
33
- import { isAddress, isLocalhost } from "./validators";
34
-
35
- export class FormoAnalytics implements IFormoAnalytics {
36
- private _provider?: EIP1193Provider;
37
- private _providerListeners: Record<string, (...args: unknown[]) => void> = {};
38
- private session: FormoAnalyticsSession;
39
- private eventManager: IEventManager;
40
- private _providers: readonly EIP6963ProviderDetail[] = [];
41
-
42
- config: Config;
43
- currentChainId?: ChainID;
44
- currentAddress?: Address = "";
45
- currentUserId?: string = "";
46
-
47
- private constructor(
48
- public readonly writeKey: string,
49
- public options: Options = {}
50
- ) {
51
- this.config = {
52
- writeKey,
53
- trackLocalhost: options.trackLocalhost || false,
54
- };
55
-
56
- this.session = new FormoAnalyticsSession();
57
- this.currentUserId =
58
- (cookie().get(SESSION_USER_ID_KEY) as string) || undefined;
59
-
60
- this.identify = this.identify.bind(this);
61
- this.connect = this.connect.bind(this);
62
- this.disconnect = this.disconnect.bind(this);
63
- this.chain = this.chain.bind(this);
64
- this.signature = this.signature.bind(this);
65
- this.transaction = this.transaction.bind(this);
66
- this.detect = this.detect.bind(this);
67
- this.track = this.track.bind(this);
68
-
69
- // Initialize logger with configuration from options
70
- Logger.init({
71
- enabled: options.logger?.enabled || false,
72
- enabledLevels: options.logger?.levels || [],
73
- });
74
-
75
- this.eventManager = new EventManager(
76
- new EventQueue(this.config.writeKey, {
77
- url: EVENTS_API_URL,
78
- flushAt: options.flushAt,
79
- retryCount: options.retryCount,
80
- maxQueueSize: options.maxQueueSize,
81
- flushInterval: options.flushInterval,
82
- })
83
- );
84
-
85
- // TODO: replace with eip6963
86
- const provider = options.provider || window?.ethereum;
87
- if (provider) {
88
- this.trackProvider(provider);
89
- }
90
-
91
- this.trackFirstPageHit();
92
- this.trackPageHits();
93
- }
94
-
95
- static async init(
96
- writeKey: string,
97
- options?: Options
98
- ): Promise<FormoAnalytics> {
99
- const analytics = new FormoAnalytics(writeKey, options);
100
-
101
- // Auto-detect wallet provider
102
- analytics._providers = await analytics.getProviders();
103
- await analytics.detectWallets(analytics._providers);
104
-
105
- return analytics;
106
- }
107
-
108
- /*
109
- Public SDK functions
110
- */
111
-
112
- /**
113
- * Emits a page visit event with the current URL information, fire on page change.
114
- * @param {string} category - The category of the page
115
- * @param {string} name - The name of the page
116
- * @param {Record<string, any>} properties - Additional properties to include
117
- * @param {Record<string, any>} context - Additional context to include
118
- * @returns {Promise<void>}
119
- */
120
- public async page(
121
- category?: string,
122
- name?: string,
123
- properties?: IFormoEventProperties,
124
- context?: IFormoEventContext
125
- ): Promise<void> {
126
- await this.trackPageHit(category, name, properties, context);
127
- }
128
-
129
- /**
130
- * Reset the current user session.
131
- * @returns {void}
132
- */
133
- public reset(): void {
134
- this.currentUserId = undefined;
135
- cookie().remove(LOCAL_ANONYMOUS_ID_KEY);
136
- cookie().remove(SESSION_USER_ID_KEY);
137
- }
138
-
139
- /**
140
- * Emits a connect wallet event.
141
- * @param {ChainID} params.chainId
142
- * @param {Address} params.address
143
- * @param {IFormoEventProperties} properties
144
- * @param {IFormoEventContext} context
145
- * @param {(...args: unknown[]) => void} callback
146
- * @throws {Error} If chainId or address is empty
147
- * @returns {Promise<void>}
148
- */
149
- async connect(
150
- {
151
- chainId,
152
- address,
153
- }: {
154
- chainId: ChainID;
155
- address: Address;
156
- },
157
- properties?: IFormoEventProperties,
158
- context?: IFormoEventContext,
159
- callback?: (...args: unknown[]) => void
160
- ): Promise<void> {
161
- if (!chainId) {
162
- logger.warn("Connect: Chain ID cannot be empty");
163
- }
164
- if (!address) {
165
- logger.warn("Connect: Address cannot be empty");
166
- }
167
-
168
- this.currentChainId = chainId;
169
- this.currentAddress = address;
170
-
171
- await this.trackEvent(
172
- EventType.CONNECT,
173
- {
174
- chainId,
175
- address,
176
- },
177
- properties,
178
- context,
179
- callback
180
- );
181
- }
182
-
183
- /**
184
- * Emits a wallet disconnect event.
185
- * @param {ChainID} params.chainId
186
- * @param {Address} params.address
187
- * @param {IFormoEventProperties} properties
188
- * @param {IFormoEventContext} context
189
- * @param {(...args: unknown[]) => void} callback
190
- * @returns {Promise<void>}
191
- */
192
- async disconnect(
193
- params?: {
194
- chainId?: ChainID;
195
- address?: Address;
196
- },
197
- properties?: IFormoEventProperties,
198
- context?: IFormoEventContext,
199
- callback?: (...args: unknown[]) => void
200
- ): Promise<void> {
201
- const address = params?.address || this.currentAddress;
202
- const chainId = params?.chainId || this.currentChainId;
203
-
204
- await this.handleDisconnect(
205
- chainId,
206
- address,
207
- properties,
208
- context,
209
- callback
210
- );
211
- }
212
-
213
- /**
214
- * Emits a chain network change event.
215
- * @param {ChainID} params.chainId
216
- * @param {Address} params.address
217
- * @param {IFormoEventProperties} properties
218
- * @param {IFormoEventContext} context
219
- * @param {(...args: unknown[]) => void} callback
220
- * @throws {Error} If chainId is empty, zero, or not a valid number
221
- * @throws {Error} If no address is provided and no previous address is recorded
222
- * @returns {Promise<void>}
223
- */
224
- async chain(
225
- {
226
- chainId,
227
- address,
228
- }: {
229
- chainId: ChainID;
230
- address?: Address;
231
- },
232
- properties?: IFormoEventProperties,
233
- context?: IFormoEventContext,
234
- callback?: (...args: unknown[]) => void
235
- ): Promise<void> {
236
- if (!chainId || Number(chainId) === 0) {
237
- throw new Error("FormoAnalytics::chain: chainId cannot be empty or 0");
238
- }
239
- if (isNaN(Number(chainId))) {
240
- throw new Error(
241
- "FormoAnalytics::chain: chainId must be a valid decimal number"
242
- );
243
- }
244
- if (!address && !this.currentAddress) {
245
- throw new Error(
246
- "FormoAnalytics::chain: address was empty and no previous address has been recorded"
247
- );
248
- }
249
-
250
- this.currentChainId = chainId;
251
-
252
- await this.trackEvent(
253
- EventType.CHAIN,
254
- {
255
- chainId,
256
- address: address || this.currentAddress,
257
- },
258
- properties,
259
- context,
260
- callback
261
- );
262
- }
263
-
264
- /**
265
- * Emits a signature event.
266
- * @param {SignatureStatus} params.status - requested, confirmed, rejected
267
- * @param {ChainID} params.chainId
268
- * @param {Address} params.address
269
- * @param {string} params.message
270
- * @param {string} params.signatureHash - only provided if status is confirmed
271
- * @param {IFormoEventProperties} properties
272
- * @param {IFormoEventContext} context
273
- * @param {(...args: unknown[]) => void} callback
274
- * @returns {Promise<void>}
275
- */
276
- async signature(
277
- {
278
- status,
279
- chainId,
280
- address,
281
- message,
282
- signatureHash,
283
- }: {
284
- status: SignatureStatus;
285
- chainId?: ChainID;
286
- address: Address;
287
- message: string;
288
- signatureHash?: string;
289
- },
290
- properties?: IFormoEventProperties,
291
- context?: IFormoEventContext,
292
- callback?: (...args: unknown[]) => void
293
- ): Promise<void> {
294
- await this.trackEvent(
295
- EventType.SIGNATURE,
296
- {
297
- status,
298
- chainId,
299
- address,
300
- message,
301
- ...(signatureHash && { signatureHash }),
302
- },
303
- properties,
304
- context,
305
- callback
306
- );
307
- }
308
-
309
- /**
310
- * Emits a transaction event.
311
- * @param {TransactionStatus} params.status - started, broadcasted, rejected
312
- * @param {ChainID} params.chainId
313
- * @param {Address} params.address
314
- * @param {string} params.data
315
- * @param {string} params.to
316
- * @param {string} params.value
317
- * @param {string} params.transactionHash - only provided if status is broadcasted
318
- * @param {IFormoEventProperties} properties
319
- * @param {IFormoEventContext} context
320
- * @param {(...args: unknown[]) => void} callback
321
- * @returns {Promise<void>}
322
- */
323
- async transaction(
324
- {
325
- status,
326
- chainId,
327
- address,
328
- data,
329
- to,
330
- value,
331
- transactionHash,
332
- }: {
333
- status: TransactionStatus;
334
- chainId: ChainID;
335
- address: Address;
336
- data?: string;
337
- to?: string;
338
- value?: string;
339
- transactionHash?: string;
340
- },
341
- properties?: IFormoEventProperties,
342
- context?: IFormoEventContext,
343
- callback?: (...args: unknown[]) => void
344
- ): Promise<void> {
345
- await this.trackEvent(
346
- EventType.TRANSACTION,
347
- {
348
- status,
349
- chainId,
350
- address,
351
- data,
352
- to,
353
- value,
354
- ...(transactionHash && { transactionHash }),
355
- },
356
- properties,
357
- context,
358
- callback
359
- );
360
- }
361
-
362
- /**
363
- * Emits an identify event with current wallet address and provider info.
364
- * @param {string} params.address
365
- * @param {string} params.userId
366
- * @param {string} params.rdns
367
- * @param {string} params.providerName
368
- * @param {IFormoEventProperties} properties
369
- * @param {IFormoEventContext} context
370
- * @param {(...args: unknown[]) => void} callback
371
- * @returns {Promise<void>}
372
- */
373
- async identify(
374
- params?: {
375
- address?: Address;
376
- providerName?: string;
377
- userId?: string;
378
- rdns?: string;
379
- },
380
- properties?: IFormoEventProperties,
381
- context?: IFormoEventContext,
382
- callback?: (...args: unknown[]) => void
383
- ): Promise<void> {
384
- try {
385
- if (!params) {
386
- // If no params provided, auto-identify
387
- logger.info("Auto-identifying with providers:", this._providers.map(p => p.info.name));
388
- for (const providerDetail of this._providers) {
389
- const provider = providerDetail.provider;
390
- if (!provider) continue;
391
-
392
- try {
393
- const address = await this.getAddress(provider);
394
- if (address) {
395
- logger.info("Auto-identifying", address, providerDetail.info.name, providerDetail.info.rdns);
396
- // NOTE: do not set this.currentAddress without explicit connect or identify
397
- await this.identify(
398
- {
399
- address,
400
- providerName: providerDetail.info.name,
401
- rdns: providerDetail.info.rdns,
402
- },
403
- properties,
404
- context,
405
- callback
406
- );
407
- }
408
- } catch (err) {
409
- logger.error(
410
- `Failed to identify provider ${providerDetail.info.name}:`,
411
- err
412
- );
413
- }
414
- }
415
- return;
416
- }
417
-
418
- // Explicit identify
419
- const { userId, address, providerName, rdns } = params;
420
- logger.info("Identify", address, userId, providerName, rdns);
421
- if (address) this.currentAddress = address;
422
- if (userId) {
423
- this.currentUserId = userId;
424
- cookie().set(SESSION_USER_ID_KEY, userId);
425
- }
426
-
427
- await this.trackEvent(
428
- EventType.IDENTIFY,
429
- {
430
- address,
431
- providerName,
432
- userId,
433
- rdns,
434
- },
435
- properties,
436
- context,
437
- callback
438
- );
439
- } catch (e) {
440
- logger.log("identify error", e);
441
- }
442
- }
443
-
444
- /**
445
- * Emits a detect wallet event with current wallet provider info.
446
- * @param {string} params.providerName
447
- * @param {string} params.rdns
448
- * @param {IFormoEventProperties} properties
449
- * @param {IFormoEventContext} context
450
- * @param {(...args: unknown[]) => void} callback
451
- * @returns {Promise<void>}
452
- */
453
- async detect(
454
- {
455
- providerName,
456
- rdns,
457
- }: {
458
- providerName: string;
459
- rdns: string;
460
- },
461
- properties?: IFormoEventProperties,
462
- context?: IFormoEventContext,
463
- callback?: (...args: unknown[]) => void
464
- ): Promise<void> {
465
- if (this.session.isWalletDetected(rdns))
466
- return logger.warn(
467
- `Detect: Wallet ${providerName} already detected in this session`
468
- );
469
-
470
- this.session.markWalletDetected(rdns);
471
- await this.trackEvent(
472
- EventType.DETECT,
473
- {
474
- providerName,
475
- rdns,
476
- },
477
- properties,
478
- context,
479
- callback
480
- );
481
- }
482
-
483
- /**
484
- * Emits a custom user event with custom properties.
485
- * @param {string} event The name of the tracked event
486
- * @param {IFormoEventProperties} properties
487
- * @param {IFormoEventContext} context
488
- * @param {(...args: unknown[]) => void} callback
489
- * @returns {Promise<void>}
490
- */
491
- async track(
492
- event: string,
493
- properties?: IFormoEventProperties,
494
- context?: IFormoEventContext,
495
- callback?: (...args: unknown[]) => void
496
- ): Promise<void> {
497
- await this.trackEvent(
498
- EventType.TRACK,
499
- { event },
500
- properties,
501
- context,
502
- callback
503
- );
504
- }
505
-
506
- /*
507
- SDK tracking and event listener functions
508
- */
509
-
510
- private trackProvider(provider: EIP1193Provider): void {
511
- try {
512
- if (provider === this._provider) {
513
- logger.warn("TrackProvider: Provider already tracked.");
514
- return;
515
- }
516
-
517
- this.currentChainId = undefined;
518
- this.currentAddress = undefined;
519
-
520
- if (this._provider) {
521
- const actions = Object.keys(this._providerListeners);
522
- for (const action of actions) {
523
- this._provider.removeListener(
524
- action,
525
- this._providerListeners[action]
526
- );
527
- delete this._providerListeners[action];
528
- }
529
- }
530
-
531
- this._provider = provider;
532
-
533
- // Register listeners for web3 provider events
534
- this.registerAddressChangedListener();
535
- this.registerChainChangedListener();
536
- this.registerSignatureListener();
537
- this.registerTransactionListener();
538
- } catch (error) {
539
- logger.error("Error tracking provider:", error);
540
- }
541
- }
542
-
543
- private registerAddressChangedListener(): void {
544
- const listener = (...args: unknown[]) =>
545
- this.onAddressChanged(args[0] as string[]);
546
-
547
- this._provider?.on("accountsChanged", listener);
548
- this._providerListeners["accountsChanged"] = listener;
549
-
550
- const onAddressDisconnected = this.onAddressDisconnected.bind(this);
551
- this._provider?.on("disconnect", onAddressDisconnected);
552
- this._providerListeners["disconnect"] = onAddressDisconnected;
553
- }
554
-
555
- private registerChainChangedListener(): void {
556
- const listener = (...args: unknown[]) =>
557
- this.onChainChanged(args[0] as string);
558
- this.provider?.on("chainChanged", listener);
559
- this._providerListeners["chainChanged"] = listener;
560
- }
561
-
562
- private registerSignatureListener(): void {
563
- if (!this.provider) {
564
- logger.error("Provider not found for signature tracking");
565
- return;
566
- }
567
- if (
568
- Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
569
- false
570
- ) {
571
- logger.warn("Provider.request is not writable");
572
- return;
573
- }
574
-
575
- const request = this.provider.request.bind(this.provider);
576
- this.provider.request = async <T>({
577
- method,
578
- params,
579
- }: RequestArguments): Promise<T | null | undefined> => {
580
- if (
581
- Array.isArray(params) &&
582
- ["eth_signTypedData_v4", "personal_sign"].includes(method)
583
- ) {
584
- // Emit signature request event
585
- this.signature({
586
- status: SignatureStatus.REQUESTED,
587
- ...this.buildSignatureEventPayload(method, params),
588
- });
589
-
590
- try {
591
- const response = (await request({ method, params })) as T;
592
- if (response) {
593
- // Emit signature confirmed event
594
- this.signature({
595
- status: SignatureStatus.CONFIRMED,
596
- ...this.buildSignatureEventPayload(method, params, response),
597
- });
598
- }
599
- return response;
600
- } catch (error) {
601
- const rpcError = error as RPCError;
602
- if (rpcError && rpcError?.code === 4001) {
603
- // Emit signature rejected event
604
- this.signature({
605
- status: SignatureStatus.REJECTED,
606
- ...this.buildSignatureEventPayload(method, params),
607
- });
608
- }
609
- throw error;
610
- }
611
- }
612
- return request({ method, params });
613
- };
614
- return;
615
- }
616
-
617
- private registerTransactionListener(): void {
618
- if (!this.provider) {
619
- logger.error("Provider not found for transaction tracking");
620
- return;
621
- }
622
- if (
623
- Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
624
- false
625
- ) {
626
- logger.warn("Provider.request is not writable");
627
- return;
628
- }
629
- const request = this.provider.request.bind(this.provider);
630
- this.provider.request = async <T>({
631
- method,
632
- params,
633
- }: RequestArguments): Promise<T | null | undefined> => {
634
- if (
635
- Array.isArray(params) &&
636
- method === "eth_sendTransaction" &&
637
- params[0]
638
- ) {
639
- // Track transaction start
640
- const payload = await this.buildTransactionEventPayload(params);
641
- this.transaction({ status: TransactionStatus.STARTED, ...payload });
642
-
643
- try {
644
- // Wait for the transaction hash
645
- const transactionHash = (await request({ method, params })) as string;
646
-
647
- // Track transaction broadcast
648
- this.transaction({
649
- status: TransactionStatus.BROADCASTED,
650
- ...payload,
651
- transactionHash,
652
- });
653
-
654
- return;
655
- } catch (error) {
656
- logger.error("Transaction error:", error);
657
- const rpcError = error as RPCError;
658
- if (rpcError && rpcError?.code === 4001) {
659
- // Emit transaction rejected event
660
- this.transaction({
661
- status: TransactionStatus.REJECTED,
662
- ...payload,
663
- });
664
- }
665
- throw error;
666
- }
667
- }
668
-
669
- return request({ method, params });
670
- };
671
-
672
- return;
673
- }
674
-
675
- private async onAddressChanged(addresses: Address[]): Promise<void> {
676
- if (addresses.length > 0) {
677
- this.onAddressConnected(addresses[0]);
678
- } else {
679
- this.onAddressDisconnected();
680
- }
681
- }
682
-
683
- private async onAddressConnected(address: Address): Promise<void> {
684
- if (address === this.currentAddress)
685
- // We have already reported this address
686
- return;
687
-
688
- this.currentAddress = address;
689
-
690
- this.currentChainId = await this.getCurrentChainId();
691
- this.connect({ chainId: this.currentChainId, address });
692
- }
693
-
694
- private async handleDisconnect(
695
- chainId?: ChainID,
696
- address?: Address,
697
- properties?: IFormoEventProperties,
698
- context?: IFormoEventContext,
699
- callback?: (...args: unknown[]) => void
700
- ): Promise<void> {
701
- const payload = {
702
- chainId: chainId || this.currentChainId,
703
- address: address || this.currentAddress,
704
- };
705
- this.currentChainId = undefined;
706
- this.currentAddress = undefined;
707
- cookie().remove(SESSION_USER_ID_KEY);
708
-
709
- await this.trackEvent(
710
- EventType.DISCONNECT,
711
- payload,
712
- properties,
713
- context,
714
- callback
715
- );
716
- }
717
-
718
- private async onAddressDisconnected(): Promise<void> {
719
- await this.handleDisconnect(this.currentChainId, this.currentAddress);
720
- }
721
-
722
- private async onChainChanged(chainIdHex: string): Promise<void> {
723
- this.currentChainId = parseInt(chainIdHex);
724
- if (!this.currentAddress) {
725
- if (!this.provider) {
726
- logger.info(
727
- "OnChainChanged: Provider not found. CHAIN_CHANGED not reported"
728
- );
729
- return Promise.resolve();
730
- }
731
-
732
- const address = await this.getAddress();
733
- if (!address) {
734
- logger.info(
735
- "OnChainChanged: Unable to fetch or store connected address"
736
- );
737
- return Promise.resolve();
738
- }
739
- this.currentAddress = address;
740
- }
741
-
742
- // Proceed only if the address exists
743
- if (this.currentAddress) {
744
- return this.chain({
745
- chainId: this.currentChainId,
746
- address: this.currentAddress,
747
- });
748
- } else {
749
- logger.info(
750
- "OnChainChanged: Current connected address is null despite fetch attempt"
751
- );
752
- }
753
- }
754
-
755
- private async trackFirstPageHit(): Promise<void> {
756
- if (cookie().get(SESSION_CURRENT_URL_KEY) === null) {
757
- cookie().set(SESSION_CURRENT_URL_KEY, window.location.href);
758
- }
759
-
760
- return this.trackPageHit();
761
- }
762
-
763
- private async trackPageHits(): Promise<void> {
764
- const oldPushState = history.pushState;
765
- history.pushState = function pushState(...args) {
766
- const ret = oldPushState.apply(this, args);
767
- window.dispatchEvent(new window.Event("locationchange"));
768
- return ret;
769
- };
770
-
771
- const oldReplaceState = history.replaceState;
772
- history.replaceState = function replaceState(...args) {
773
- const ret = oldReplaceState.apply(this, args);
774
- window.dispatchEvent(new window.Event("locationchange"));
775
- return ret;
776
- };
777
-
778
- window.addEventListener("popstate", () => this.onLocationChange());
779
- window.addEventListener("locationchange", () => this.onLocationChange());
780
- }
781
-
782
- private async onLocationChange(): Promise<void> {
783
- const currentUrl = cookie().get(SESSION_CURRENT_URL_KEY);
784
-
785
- if (currentUrl !== window.location.href) {
786
- cookie().set(SESSION_CURRENT_URL_KEY, window.location.href);
787
- this.trackPageHit();
788
- }
789
- }
790
-
791
- private async trackPageHit(
792
- category?: string,
793
- name?: string,
794
- properties?: IFormoEventProperties,
795
- context?: IFormoEventContext,
796
- callback?: (...args: unknown[]) => void
797
- ): Promise<void> {
798
- if (!this.config.trackLocalhost && isLocalhost()) {
799
- return logger.warn(
800
- "Track page hit: Ignoring event because website is running locally"
801
- );
802
- }
803
-
804
- setTimeout(async () => {
805
- this.trackEvent(
806
- EventType.PAGE,
807
- {
808
- category,
809
- name,
810
- },
811
- properties,
812
- context,
813
- callback
814
- );
815
- }, 300);
816
- }
817
-
818
- private async trackEvent(
819
- type: TEventType,
820
- payload?: any,
821
- properties?: IFormoEventProperties,
822
- context?: IFormoEventContext,
823
- callback?: (...args: unknown[]) => void
824
- ): Promise<void> {
825
- try {
826
- this.eventManager.addEvent(
827
- {
828
- type,
829
- ...payload,
830
- properties,
831
- context,
832
- callback,
833
- },
834
- this.currentAddress,
835
- this.currentUserId
836
- );
837
- } catch (error) {
838
- logger.error("Error tracking event:", error);
839
- }
840
- }
841
-
842
- /*
843
- Utility functions
844
- */
845
-
846
- private async getProviders(): Promise<readonly EIP6963ProviderDetail[]> {
847
- const store = createStore();
848
- let providers = store.getProviders();
849
- store.subscribe((providerDetails) => {
850
- providers = providerDetails;
851
- this._providers = providers;
852
- });
853
-
854
- // Fallback to injected provider if no providers are found
855
- if (providers.length === 0) {
856
- this._providers = window?.ethereum ? [window.ethereum] : [];
857
- return this._providers;
858
- }
859
- this._providers = providers;
860
- return providers;
861
- }
862
-
863
- get providers(): readonly EIP6963ProviderDetail[] {
864
- return this._providers;
865
- }
866
-
867
- private async detectWallets(
868
- providers: readonly EIP6963ProviderDetail[]
869
- ): Promise<void> {
870
- try {
871
- for (const eip6963ProviderDetail of providers) {
872
- await this.detect({
873
- providerName: eip6963ProviderDetail?.info.name,
874
- rdns: eip6963ProviderDetail?.info.rdns,
875
- });
876
- }
877
- } catch (err) {
878
- logger.error("Error detect all wallets:", err);
879
- }
880
- }
881
-
882
- get provider(): EIP1193Provider | undefined {
883
- return this._provider;
884
- }
885
-
886
- private async getAddress(
887
- provider?: EIP1193Provider
888
- ): Promise<Address | null> {
889
- if (this.currentAddress) return this.currentAddress;
890
- const p = provider || this.provider;
891
- if (!p) {
892
- logger.info("The provider is not set");
893
- return null;
894
- }
895
-
896
- try {
897
- const accounts = await this.getAccounts(p);
898
- if (accounts && accounts.length > 0) {
899
- if (isAddress(accounts[0])) {
900
- return accounts[0];
901
- }
902
- }
903
- } catch (err) {
904
- logger.error("Failed to fetch accounts from provider:", err);
905
- return null;
906
- }
907
- return null;
908
- }
909
-
910
- private async getAccounts(
911
- provider?: EIP1193Provider
912
- ): Promise<Address[] | null> {
913
- const p = provider || this.provider;
914
- try {
915
- const res: string[] | null | undefined = await p?.request({
916
- method: "eth_accounts",
917
- });
918
- if (!res || res.length === 0) return null;
919
- return res.filter((e) => isAddress(e));
920
- } catch (err) {
921
- if ((err as any).code !== 4001) {
922
- logger.error(
923
- "FormoAnalytics::getAccounts: eth_accounts threw an error",
924
- err
925
- );
926
- }
927
- return null;
928
- }
929
- }
930
-
931
- private async getCurrentChainId(): Promise<number> {
932
- if (!this.provider) {
933
- logger.error("Provider not set for chain ID");
934
- }
935
-
936
- let chainIdHex;
937
- try {
938
- chainIdHex = await this.provider?.request<string>({
939
- method: "eth_chainId",
940
- });
941
- if (!chainIdHex) {
942
- logger.info("Chain id not found");
943
- return 0;
944
- }
945
- return parseInt(chainIdHex as string, 16);
946
- } catch (err) {
947
- logger.error("eth_chainId threw an error:", err);
948
- return 0;
949
- }
950
- }
951
-
952
- private buildSignatureEventPayload(
953
- method: string,
954
- params: unknown[],
955
- response?: unknown
956
- ) {
957
- const basePayload = {
958
- chainId: this.currentChainId,
959
- address:
960
- method === "personal_sign"
961
- ? (params[1] as Address)
962
- : (params[0] as Address),
963
- };
964
-
965
- if (method === "personal_sign") {
966
- const message = Buffer.from(
967
- (params[0] as string).slice(2),
968
- "hex"
969
- ).toString("utf8");
970
- return {
971
- ...basePayload,
972
- message,
973
- ...(response ? { signatureHash: response as string } : {}),
974
- };
975
- }
976
-
977
- return {
978
- ...basePayload,
979
- message: params[1] as string,
980
- ...(response ? { signatureHash: response as string } : {}),
981
- };
982
- }
983
-
984
- private async buildTransactionEventPayload(params: unknown[]) {
985
- const { data, from, to, value } = params[0] as {
986
- data: string;
987
- from: string;
988
- to: string;
989
- value: string;
990
- };
991
- return {
992
- chainId: this.currentChainId || (await this.getCurrentChainId()),
993
- data,
994
- address: from,
995
- to,
996
- value,
997
- };
998
- }
999
- }
1000
-
1001
- interface IFormoAnalyticsSession {
1002
- isWalletDetected(rdns: string): boolean;
1003
- markWalletDetected(rdns: string): void;
1004
- }
1005
-
1006
- class FormoAnalyticsSession implements IFormoAnalyticsSession {
1007
- public isWalletDetected(rdns: string): boolean {
1008
- const rdnses = cookie().get(SESSION_WALLET_DETECTED_KEY)?.split(",") || [];
1009
- return rdnses.includes(rdns);
1010
- }
1011
-
1012
- public markWalletDetected(rdns: string): void {
1013
- const rdnses = cookie().get(SESSION_WALLET_DETECTED_KEY)?.split(",") || [];
1014
- rdnses.push(rdns);
1015
- cookie().set(SESSION_WALLET_DETECTED_KEY, rdnses.join(","), {
1016
- // by the end of the day
1017
- expires: new Date(Date.now() + 86400 * 1000).toUTCString(),
1018
- path: "/",
1019
- });
1020
- }
1021
- }