@formo/analytics 1.13.0-alpha.1 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/FormoAnalytics.d.ts +91 -24
- package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -1
- package/dist/cjs/src/FormoAnalytics.js +325 -37
- package/dist/cjs/src/FormoAnalytics.js.map +1 -1
- package/dist/cjs/src/constants/config.d.ts +522 -522
- package/dist/cjs/src/constants/config.d.ts.map +1 -1
- package/dist/cjs/src/constants/config.js +557 -557
- package/dist/cjs/src/constants/config.js.map +1 -1
- package/dist/cjs/src/constants/events.d.ts +1 -1
- package/dist/cjs/src/constants/events.d.ts.map +1 -1
- package/dist/cjs/src/constants/events.js +1 -1
- package/dist/cjs/src/constants/events.js.map +1 -1
- package/dist/cjs/src/types/base.d.ts +1 -1
- package/dist/cjs/src/types/base.d.ts.map +1 -1
- package/dist/cjs/src/types/events.d.ts +11 -0
- package/dist/cjs/src/types/events.d.ts.map +1 -0
- package/dist/cjs/src/types/events.js +16 -0
- package/dist/cjs/src/types/events.js.map +1 -0
- package/dist/cjs/src/types/index.d.ts +2 -1
- package/dist/cjs/src/types/index.d.ts.map +1 -1
- package/dist/cjs/src/types/index.js +2 -1
- package/dist/cjs/src/types/index.js.map +1 -1
- package/dist/cjs/src/types/{wallet.d.ts → provider.d.ts} +5 -1
- package/dist/cjs/src/types/provider.d.ts.map +1 -0
- package/dist/cjs/src/types/{wallet.js → provider.js} +1 -1
- package/dist/cjs/src/types/provider.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/src/FormoAnalytics.d.ts +91 -24
- package/dist/esm/src/FormoAnalytics.d.ts.map +1 -1
- package/dist/esm/src/FormoAnalytics.js +326 -38
- package/dist/esm/src/FormoAnalytics.js.map +1 -1
- package/dist/esm/src/constants/config.d.ts +522 -522
- package/dist/esm/src/constants/config.d.ts.map +1 -1
- package/dist/esm/src/constants/config.js +556 -556
- package/dist/esm/src/constants/config.js.map +1 -1
- package/dist/esm/src/constants/events.d.ts +1 -1
- package/dist/esm/src/constants/events.d.ts.map +1 -1
- package/dist/esm/src/constants/events.js +1 -1
- package/dist/esm/src/constants/events.js.map +1 -1
- package/dist/esm/src/types/base.d.ts +1 -1
- package/dist/esm/src/types/base.d.ts.map +1 -1
- package/dist/esm/src/types/events.d.ts +11 -0
- package/dist/esm/src/types/events.d.ts.map +1 -0
- package/dist/esm/src/types/events.js +13 -0
- package/dist/esm/src/types/events.js.map +1 -0
- package/dist/esm/src/types/index.d.ts +2 -1
- package/dist/esm/src/types/index.d.ts.map +1 -1
- package/dist/esm/src/types/index.js +2 -1
- package/dist/esm/src/types/index.js.map +1 -1
- package/dist/esm/src/types/{wallet.d.ts → provider.d.ts} +5 -1
- package/dist/esm/src/types/provider.d.ts.map +1 -0
- package/dist/esm/src/types/provider.js +2 -0
- package/dist/esm/src/types/provider.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/FormoAnalytics.ts +418 -69
- package/src/constants/config.ts +556 -556
- package/src/constants/events.ts +1 -1
- package/src/types/base.ts +1 -1
- package/src/types/events.ts +11 -0
- package/src/types/index.ts +2 -1
- package/src/types/{wallet.ts → provider.ts} +5 -0
- package/dist/cjs/src/types/wallet.d.ts.map +0 -1
- package/dist/cjs/src/types/wallet.js.map +0 -1
- package/dist/esm/src/types/wallet.d.ts.map +0 -1
- package/dist/esm/src/types/wallet.js +0 -2
- package/dist/esm/src/types/wallet.js.map +0 -1
package/package.json
CHANGED
package/src/FormoAnalytics.ts
CHANGED
|
@@ -1,45 +1,66 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import {
|
|
3
3
|
COUNTRY_LIST,
|
|
4
|
+
CURRENT_URL_KEY,
|
|
4
5
|
EVENTS_API_URL,
|
|
5
6
|
Event,
|
|
6
7
|
} from "./constants";
|
|
7
8
|
import { H } from "highlight.run";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ChainID,
|
|
11
|
+
Address,
|
|
12
|
+
EIP1193Provider,
|
|
13
|
+
Options,
|
|
14
|
+
Config,
|
|
15
|
+
RequestArguments,
|
|
16
|
+
RPCError,
|
|
17
|
+
SignatureStatus,
|
|
18
|
+
TransactionStatus,
|
|
19
|
+
} from "./types";
|
|
9
20
|
|
|
10
21
|
interface IFormoAnalytics {
|
|
11
|
-
/**
|
|
12
|
-
* Tracks page visit events.
|
|
13
|
-
*/
|
|
14
22
|
page(): void;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
// trackPagesChange(): void;
|
|
24
|
+
connect(params: { chainId: ChainID; address: Address }): Promise<void>;
|
|
25
|
+
disconnect(params?: { chainId?: ChainID; address?: Address }): Promise<void>;
|
|
26
|
+
chain(params: { chainId: ChainID; address?: Address }): Promise<void>;
|
|
27
|
+
signature({
|
|
28
|
+
status,
|
|
29
|
+
chainId,
|
|
30
|
+
address,
|
|
31
|
+
message,
|
|
32
|
+
signatureHash,
|
|
33
|
+
}: {
|
|
34
|
+
status: SignatureStatus;
|
|
35
|
+
chainId?: ChainID;
|
|
36
|
+
address: Address;
|
|
37
|
+
message: string;
|
|
38
|
+
signatureHash?: string;
|
|
39
|
+
}): Promise<void>;
|
|
40
|
+
transaction({
|
|
41
|
+
status,
|
|
42
|
+
chainId,
|
|
43
|
+
address,
|
|
44
|
+
data,
|
|
45
|
+
to,
|
|
46
|
+
value,
|
|
47
|
+
transactionHash,
|
|
48
|
+
}: {
|
|
49
|
+
status: TransactionStatus;
|
|
50
|
+
chainId: ChainID;
|
|
51
|
+
address: Address;
|
|
52
|
+
data?: string;
|
|
53
|
+
to?: string;
|
|
54
|
+
value?: string;
|
|
55
|
+
transactionHash?: string;
|
|
56
|
+
}): Promise<void>;
|
|
57
|
+
identify(params: { address: Address }): Promise<void>;
|
|
58
|
+
track(action: string, payload: Record<string, any>): Promise<void>;
|
|
35
59
|
}
|
|
36
60
|
|
|
37
61
|
export class FormoAnalytics implements IFormoAnalytics {
|
|
38
62
|
private _provider?: EIP1193Provider;
|
|
39
|
-
private _providerListeners: Record<
|
|
40
|
-
string,
|
|
41
|
-
(...args: unknown[]) => void
|
|
42
|
-
> = {};
|
|
63
|
+
private _providerListeners: Record<string, (...args: unknown[]) => void> = {};
|
|
43
64
|
|
|
44
65
|
config: Config;
|
|
45
66
|
currentChainId?: ChainID;
|
|
@@ -53,11 +74,15 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
53
74
|
apiKey: apiKey,
|
|
54
75
|
};
|
|
55
76
|
|
|
77
|
+
// TODO: replace with eip6963
|
|
56
78
|
const provider =
|
|
57
79
|
window?.ethereum || window.web3?.currentProvider || options?.provider;
|
|
58
80
|
if (provider) {
|
|
59
81
|
this.trackProvider(provider);
|
|
60
82
|
}
|
|
83
|
+
|
|
84
|
+
this.trackFirstPageVisit();
|
|
85
|
+
this.trackPagesChange();
|
|
61
86
|
}
|
|
62
87
|
|
|
63
88
|
static async init(
|
|
@@ -66,14 +91,40 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
66
91
|
): Promise<FormoAnalytics> {
|
|
67
92
|
// May be needed for delayed loading
|
|
68
93
|
// https://github.com/segmentio/analytics-next/tree/master/packages/browser#lazy--delayed-loading
|
|
69
|
-
|
|
94
|
+
const analytics = new FormoAnalytics(apiKey, options);
|
|
95
|
+
|
|
96
|
+
// Identify current user on init (TODO: make this toggleable)
|
|
97
|
+
analytics.identify();
|
|
98
|
+
|
|
99
|
+
return analytics;
|
|
70
100
|
}
|
|
71
101
|
|
|
72
102
|
/*
|
|
73
103
|
Public SDK functions
|
|
74
104
|
*/
|
|
75
105
|
|
|
76
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Emits a page visit event with the current URL information, fire on page change.
|
|
108
|
+
* @returns {Promise<void>}
|
|
109
|
+
*/
|
|
110
|
+
async page(): Promise<void> {
|
|
111
|
+
await this.trackPageHit();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Emits a wallet connect event.
|
|
116
|
+
* @param {ChainID} params.chainId
|
|
117
|
+
* @param {Address} params.address
|
|
118
|
+
* @throws {Error} If chainId or address is empty
|
|
119
|
+
* @returns {Promise<void>}
|
|
120
|
+
*/
|
|
121
|
+
async connect({
|
|
122
|
+
chainId,
|
|
123
|
+
address,
|
|
124
|
+
}: {
|
|
125
|
+
chainId: ChainID;
|
|
126
|
+
address: Address;
|
|
127
|
+
}): Promise<void> {
|
|
77
128
|
if (!chainId) {
|
|
78
129
|
throw new Error("FormoAnalytics::connect: chain ID cannot be empty");
|
|
79
130
|
}
|
|
@@ -90,24 +141,48 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
90
141
|
});
|
|
91
142
|
}
|
|
92
143
|
|
|
93
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Emits a wallet disconnect event.
|
|
146
|
+
* @param {ChainID} params.chainId
|
|
147
|
+
* @param {Address} params.address
|
|
148
|
+
* @returns {Promise<void>}
|
|
149
|
+
*/
|
|
150
|
+
async disconnect(params?: {
|
|
151
|
+
chainId?: ChainID;
|
|
152
|
+
address?: Address;
|
|
153
|
+
}): Promise<void> {
|
|
94
154
|
const address = params?.address || this.currentConnectedAddress;
|
|
95
155
|
const chainId = params?.chainId || this.currentChainId;
|
|
156
|
+
|
|
96
157
|
await this.handleDisconnect(chainId, address);
|
|
97
158
|
}
|
|
98
159
|
|
|
99
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Emits a chain network change event.
|
|
162
|
+
* @param {ChainID} params.chainId
|
|
163
|
+
* @param {Address} params.address
|
|
164
|
+
* @throws {Error} If chainId is empty, zero, or not a valid number
|
|
165
|
+
* @throws {Error} If no address is provided and no previous address is recorded
|
|
166
|
+
* @returns {Promise<void>}
|
|
167
|
+
*/
|
|
168
|
+
async chain({
|
|
169
|
+
chainId,
|
|
170
|
+
address,
|
|
171
|
+
}: {
|
|
172
|
+
chainId: ChainID;
|
|
173
|
+
address?: Address;
|
|
174
|
+
}): Promise<void> {
|
|
100
175
|
if (!chainId || Number(chainId) === 0) {
|
|
101
176
|
throw new Error("FormoAnalytics::chain: chainId cannot be empty or 0");
|
|
102
177
|
}
|
|
103
|
-
if (
|
|
178
|
+
if (isNaN(Number(chainId))) {
|
|
104
179
|
throw new Error(
|
|
105
|
-
"FormoAnalytics::chain:
|
|
180
|
+
"FormoAnalytics::chain: chainId must be a valid decimal number"
|
|
106
181
|
);
|
|
107
182
|
}
|
|
108
|
-
if (
|
|
183
|
+
if (!address && !this.currentConnectedAddress) {
|
|
109
184
|
throw new Error(
|
|
110
|
-
"FormoAnalytics::chain:
|
|
185
|
+
"FormoAnalytics::chain: address was empty and no previous address has been recorded"
|
|
111
186
|
);
|
|
112
187
|
}
|
|
113
188
|
|
|
@@ -119,13 +194,77 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
119
194
|
});
|
|
120
195
|
}
|
|
121
196
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
197
|
+
async signature({
|
|
198
|
+
status,
|
|
199
|
+
chainId,
|
|
200
|
+
address,
|
|
201
|
+
message,
|
|
202
|
+
signatureHash,
|
|
203
|
+
}: {
|
|
204
|
+
status: SignatureStatus;
|
|
205
|
+
chainId?: ChainID;
|
|
206
|
+
address: Address;
|
|
207
|
+
message: string;
|
|
208
|
+
signatureHash?: string;
|
|
209
|
+
}): Promise<void> {
|
|
210
|
+
await this.trackEvent(Event.SIGNATURE, {
|
|
211
|
+
status,
|
|
212
|
+
chainId,
|
|
213
|
+
address,
|
|
214
|
+
message,
|
|
215
|
+
...(signatureHash && { signatureHash }),
|
|
216
|
+
});
|
|
125
217
|
}
|
|
126
218
|
|
|
127
|
-
async
|
|
128
|
-
|
|
219
|
+
async transaction({
|
|
220
|
+
status,
|
|
221
|
+
chainId,
|
|
222
|
+
address,
|
|
223
|
+
data,
|
|
224
|
+
to,
|
|
225
|
+
value,
|
|
226
|
+
transactionHash,
|
|
227
|
+
}: {
|
|
228
|
+
status: TransactionStatus;
|
|
229
|
+
chainId: ChainID;
|
|
230
|
+
address: Address;
|
|
231
|
+
data?: string;
|
|
232
|
+
to?: string;
|
|
233
|
+
value?: string;
|
|
234
|
+
transactionHash?: string;
|
|
235
|
+
}): Promise<void> {
|
|
236
|
+
await this.trackEvent(Event.TRANSACTION, {
|
|
237
|
+
status,
|
|
238
|
+
chainId,
|
|
239
|
+
address,
|
|
240
|
+
data,
|
|
241
|
+
to,
|
|
242
|
+
value,
|
|
243
|
+
...(transactionHash && { transactionHash }),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Emits a identify event with current wallet address.
|
|
249
|
+
* @param {Address} params.address
|
|
250
|
+
* @returns {Promise<void>}
|
|
251
|
+
*/
|
|
252
|
+
public async identify(params?: { address: Address }): Promise<void> {
|
|
253
|
+
const address = params?.address || (await this.getAddress());
|
|
254
|
+
await this.trackEvent(Event.IDENTIFY, {
|
|
255
|
+
address,
|
|
256
|
+
// TODO: detect wallet type https://linear.app/getformo/issue/P-837/sdk-detect-user-wallet-type-in-identify-call
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Emits a custom event with custom data.
|
|
262
|
+
* @param {string} action
|
|
263
|
+
* @param {Record<string, any>} payload
|
|
264
|
+
* @returns {Promise<void>}
|
|
265
|
+
*/
|
|
266
|
+
async track(action: string, payload: Record<string, any>): Promise<void> {
|
|
267
|
+
await this.trackEvent(action, payload);
|
|
129
268
|
}
|
|
130
269
|
|
|
131
270
|
/*
|
|
@@ -142,24 +281,20 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
142
281
|
this.currentConnectedAddress = undefined;
|
|
143
282
|
|
|
144
283
|
if (this._provider) {
|
|
145
|
-
const
|
|
146
|
-
for (const
|
|
147
|
-
this._provider.removeListener(
|
|
148
|
-
|
|
149
|
-
this._providerListeners[eventName]
|
|
150
|
-
);
|
|
151
|
-
delete this._providerListeners[eventName];
|
|
284
|
+
const actions = Object.keys(this._providerListeners);
|
|
285
|
+
for (const action of actions) {
|
|
286
|
+
this._provider.removeListener(action, this._providerListeners[action]);
|
|
287
|
+
delete this._providerListeners[action];
|
|
152
288
|
}
|
|
153
289
|
}
|
|
154
290
|
|
|
155
|
-
console.log("Tracking new provider:", provider);
|
|
156
291
|
this._provider = provider;
|
|
157
292
|
|
|
158
|
-
|
|
293
|
+
// Register listeners for web3 provider events
|
|
159
294
|
this.registerAddressChangedListener();
|
|
160
295
|
this.registerChainChangedListener();
|
|
161
|
-
|
|
162
|
-
|
|
296
|
+
this.registerSignatureListener();
|
|
297
|
+
this.registerTransactionListener();
|
|
163
298
|
}
|
|
164
299
|
|
|
165
300
|
private registerAddressChangedListener(): void {
|
|
@@ -181,6 +316,120 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
181
316
|
this._providerListeners["chainChanged"] = listener;
|
|
182
317
|
}
|
|
183
318
|
|
|
319
|
+
private registerSignatureListener(): void {
|
|
320
|
+
if (!this.provider) {
|
|
321
|
+
console.error("_trackSigning: provider not found");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (
|
|
325
|
+
Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
|
|
326
|
+
false
|
|
327
|
+
) {
|
|
328
|
+
console.warn("_trackSigning: provider.request is not writable");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const request = this.provider.request.bind(this.provider);
|
|
333
|
+
this.provider.request = async <T>({
|
|
334
|
+
method,
|
|
335
|
+
params,
|
|
336
|
+
}: RequestArguments): Promise<T | null | undefined> => {
|
|
337
|
+
if (
|
|
338
|
+
Array.isArray(params) &&
|
|
339
|
+
["eth_signTypedData_v4", "personal_sign"].includes(method)
|
|
340
|
+
) {
|
|
341
|
+
// Emit signature request event
|
|
342
|
+
this.signature({
|
|
343
|
+
status: SignatureStatus.REQUESTED,
|
|
344
|
+
...this.buildSignatureEventPayload(method, params),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const response = (await request({ method, params })) as T;
|
|
349
|
+
if (response) {
|
|
350
|
+
// Emit signature confirmed event
|
|
351
|
+
this.signature({
|
|
352
|
+
status: SignatureStatus.CONFIRMED,
|
|
353
|
+
...this.buildSignatureEventPayload(method, params, response),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return response;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
const rpcError = error as RPCError;
|
|
359
|
+
if (rpcError && rpcError?.code === 4001) {
|
|
360
|
+
// Emit signature rejected event
|
|
361
|
+
this.signature({
|
|
362
|
+
status: SignatureStatus.REJECTED,
|
|
363
|
+
...this.buildSignatureEventPayload(method, params),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return request({ method, params });
|
|
370
|
+
};
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private registerTransactionListener(): void {
|
|
375
|
+
if (!this.provider) {
|
|
376
|
+
console.error("_trackTransactions: provider not found");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (
|
|
380
|
+
Object.getOwnPropertyDescriptor(this.provider, "request")?.writable ===
|
|
381
|
+
false
|
|
382
|
+
) {
|
|
383
|
+
console.warn("_trackTransactions: provider.request is not writable");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const request = this.provider.request.bind(this.provider);
|
|
387
|
+
this.provider.request = async <T>({
|
|
388
|
+
method,
|
|
389
|
+
params,
|
|
390
|
+
}: RequestArguments): Promise<T | null | undefined> => {
|
|
391
|
+
if (
|
|
392
|
+
Array.isArray(params) &&
|
|
393
|
+
method === "eth_sendTransaction" &&
|
|
394
|
+
params[0]
|
|
395
|
+
) {
|
|
396
|
+
// Track transaction start
|
|
397
|
+
const payload = await this.buildTransactionEventPayload(params);
|
|
398
|
+
this.transaction({ status: TransactionStatus.STARTED, ...payload });
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
// Wait for the transaction hash
|
|
402
|
+
const transactionHash = (await request({ method, params })) as string;
|
|
403
|
+
|
|
404
|
+
// Track transaction broadcast
|
|
405
|
+
this.transaction({
|
|
406
|
+
status: TransactionStatus.BROADCASTED,
|
|
407
|
+
...payload,
|
|
408
|
+
transactionHash,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
return;
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.log("transaction listener catch");
|
|
414
|
+
console.log(error);
|
|
415
|
+
const rpcError = error as RPCError;
|
|
416
|
+
if (rpcError && rpcError?.code === 4001) {
|
|
417
|
+
// Emit transaction rejected event
|
|
418
|
+
this.transaction({
|
|
419
|
+
status: TransactionStatus.REJECTED,
|
|
420
|
+
...payload,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
throw error;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return request({ method, params });
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
184
433
|
private async onAddressChanged(addresses: Address[]): Promise<void> {
|
|
185
434
|
if (addresses.length > 0) {
|
|
186
435
|
this.onAddressConnected(addresses[0]);
|
|
@@ -201,18 +450,25 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
201
450
|
this.connect({ chainId: this.currentChainId, address });
|
|
202
451
|
}
|
|
203
452
|
|
|
204
|
-
private async handleDisconnect(
|
|
453
|
+
private async handleDisconnect(
|
|
454
|
+
chainId?: ChainID,
|
|
455
|
+
address?: Address
|
|
456
|
+
): Promise<void> {
|
|
205
457
|
const payload = {
|
|
206
458
|
chain_id: chainId || this.currentChainId,
|
|
207
459
|
address: address || this.currentConnectedAddress,
|
|
208
460
|
};
|
|
209
461
|
this.currentChainId = undefined;
|
|
210
462
|
this.currentConnectedAddress = undefined;
|
|
463
|
+
|
|
211
464
|
await this.trackEvent(Event.DISCONNECT, payload);
|
|
212
465
|
}
|
|
213
466
|
|
|
214
467
|
private async onAddressDisconnected(): Promise<void> {
|
|
215
|
-
await this.handleDisconnect(
|
|
468
|
+
await this.handleDisconnect(
|
|
469
|
+
this.currentChainId,
|
|
470
|
+
this.currentConnectedAddress
|
|
471
|
+
);
|
|
216
472
|
}
|
|
217
473
|
|
|
218
474
|
private async onChainChanged(chainIdHex: string): Promise<void> {
|
|
@@ -225,7 +481,6 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
225
481
|
return Promise.resolve();
|
|
226
482
|
}
|
|
227
483
|
|
|
228
|
-
// Attempt to fetch and store the connected address
|
|
229
484
|
const address = await this.getAddress();
|
|
230
485
|
if (!address) {
|
|
231
486
|
console.log(
|
|
@@ -234,7 +489,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
234
489
|
return Promise.resolve();
|
|
235
490
|
}
|
|
236
491
|
|
|
237
|
-
this.currentConnectedAddress = address
|
|
492
|
+
this.currentConnectedAddress = address;
|
|
238
493
|
}
|
|
239
494
|
|
|
240
495
|
// Proceed only if the address exists
|
|
@@ -250,22 +505,60 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
250
505
|
}
|
|
251
506
|
}
|
|
252
507
|
|
|
253
|
-
|
|
508
|
+
private async trackFirstPageVisit(): Promise<void> {
|
|
509
|
+
if (sessionStorage.getItem(CURRENT_URL_KEY) === null) {
|
|
510
|
+
sessionStorage.setItem(CURRENT_URL_KEY, window.location.href);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return this.trackPageHit();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
private async trackPagesChange(): Promise<void> {
|
|
517
|
+
const oldPushState = history.pushState;
|
|
518
|
+
history.pushState = function pushState(...args) {
|
|
519
|
+
const ret = oldPushState.apply(this, args);
|
|
520
|
+
window.dispatchEvent(new window.Event("locationchange"));
|
|
521
|
+
return ret;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const oldReplaceState = history.replaceState;
|
|
525
|
+
history.replaceState = function replaceState(...args) {
|
|
526
|
+
const ret = oldReplaceState.apply(this, args);
|
|
527
|
+
window.dispatchEvent(new window.Event("locationchange"));
|
|
528
|
+
return ret;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
window.addEventListener("popstate", () => this.onLocationChange());
|
|
532
|
+
|
|
533
|
+
window.addEventListener("locationchange", () => this.onLocationChange());
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private async onLocationChange(): Promise<void> {
|
|
537
|
+
const currentUrl = sessionStorage.getItem(CURRENT_URL_KEY);
|
|
538
|
+
|
|
539
|
+
if (currentUrl !== window.location.href) {
|
|
540
|
+
sessionStorage.setItem(CURRENT_URL_KEY, window.location.href);
|
|
541
|
+
this.trackPageHit();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
254
545
|
// TODO: Add event listener and support for SPA and hash-based navigation
|
|
255
546
|
// https://linear.app/getformo/issue/P-800/sdk-support-spa-and-hash-based-routing
|
|
256
547
|
private trackPageHit(): void {
|
|
257
548
|
const pathname = window.location.pathname;
|
|
258
549
|
const href = window.location.href;
|
|
550
|
+
const hash = window.location.hash;
|
|
259
551
|
|
|
260
552
|
setTimeout(async () => {
|
|
261
553
|
this.trackEvent(Event.PAGE, {
|
|
262
554
|
pathname,
|
|
263
555
|
href,
|
|
556
|
+
hash,
|
|
264
557
|
});
|
|
265
558
|
}, 300);
|
|
266
559
|
}
|
|
267
560
|
|
|
268
|
-
// TODO: refactor this with event queue and flushing
|
|
561
|
+
// TODO: refactor this with event queue and flushing
|
|
269
562
|
// https://linear.app/getformo/issue/P-835/sdk-refactor-retries-with-event-queue-and-batching
|
|
270
563
|
private async trackEvent(action: string, payload: any): Promise<void> {
|
|
271
564
|
const address = await this.getAddress();
|
|
@@ -291,7 +584,12 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
291
584
|
);
|
|
292
585
|
|
|
293
586
|
if (response.status >= 200 && response.status < 300) {
|
|
294
|
-
console.log(
|
|
587
|
+
console.log(
|
|
588
|
+
`Event sent successfully: ${this.getActionDescriptor(
|
|
589
|
+
action,
|
|
590
|
+
payload
|
|
591
|
+
)}`
|
|
592
|
+
);
|
|
295
593
|
} else {
|
|
296
594
|
throw new Error(`Failed with status: ${response.status}`);
|
|
297
595
|
}
|
|
@@ -310,9 +608,10 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
310
608
|
|
|
311
609
|
get provider(): EIP1193Provider | undefined {
|
|
312
610
|
return this._provider;
|
|
313
|
-
}
|
|
611
|
+
}
|
|
314
612
|
|
|
315
613
|
private async getAddress(): Promise<Address | null> {
|
|
614
|
+
if (this.currentConnectedAddress) return this.currentConnectedAddress;
|
|
316
615
|
if (!this.provider) {
|
|
317
616
|
console.log("FormoAnalytics::getAddress: the provider is not set");
|
|
318
617
|
return null;
|
|
@@ -321,11 +620,9 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
321
620
|
try {
|
|
322
621
|
const accounts = await this.getAccounts();
|
|
323
622
|
if (accounts && accounts.length > 0) {
|
|
324
|
-
|
|
623
|
+
return accounts[0];
|
|
325
624
|
// TODO: how to handle multiple addresses? Should we emit a connect event here? Since the user has not manually connected
|
|
326
625
|
// https://linear.app/getformo/issue/P-691/sdk-detect-multiple-wallets-using-eip6963
|
|
327
|
-
this.onAddressConnected(address);
|
|
328
|
-
return address;
|
|
329
626
|
}
|
|
330
627
|
} catch (err) {
|
|
331
628
|
console.log("Failed to fetch accounts from provider:", err);
|
|
@@ -368,9 +665,7 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
368
665
|
method: "eth_chainId",
|
|
369
666
|
});
|
|
370
667
|
if (!chainIdHex) {
|
|
371
|
-
console.log(
|
|
372
|
-
"FormoAnalytics::fetchChainId: chain id not found"
|
|
373
|
-
);
|
|
668
|
+
console.log("FormoAnalytics::fetchChainId: chain id not found");
|
|
374
669
|
return 0;
|
|
375
670
|
}
|
|
376
671
|
return parseInt(chainIdHex as string, 16);
|
|
@@ -386,10 +681,12 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
386
681
|
private getLocation(): string | undefined {
|
|
387
682
|
try {
|
|
388
683
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
389
|
-
|
|
684
|
+
if (timezone in COUNTRY_LIST)
|
|
685
|
+
return COUNTRY_LIST[timezone as keyof typeof COUNTRY_LIST];
|
|
686
|
+
return timezone;
|
|
390
687
|
} catch (error) {
|
|
391
688
|
console.error("Error resolving timezone:", error);
|
|
392
|
-
return
|
|
689
|
+
return "";
|
|
393
690
|
}
|
|
394
691
|
}
|
|
395
692
|
|
|
@@ -430,5 +727,57 @@ export class FormoAnalytics implements IFormoAnalytics {
|
|
|
430
727
|
ref: params.get("ref"),
|
|
431
728
|
...eventSpecificPayload,
|
|
432
729
|
};
|
|
433
|
-
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
private buildSignatureEventPayload(
|
|
733
|
+
method: string,
|
|
734
|
+
params: unknown[],
|
|
735
|
+
response?: unknown
|
|
736
|
+
) {
|
|
737
|
+
const basePayload = {
|
|
738
|
+
chainId: this.currentChainId,
|
|
739
|
+
address:
|
|
740
|
+
method === "personal_sign"
|
|
741
|
+
? (params[1] as Address)
|
|
742
|
+
: (params[0] as Address),
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
if (method === "personal_sign") {
|
|
746
|
+
const message = Buffer.from(
|
|
747
|
+
(params[0] as string).slice(2),
|
|
748
|
+
"hex"
|
|
749
|
+
).toString("utf8");
|
|
750
|
+
return {
|
|
751
|
+
...basePayload,
|
|
752
|
+
message,
|
|
753
|
+
...(response ? { signatureHash: response as string } : {}),
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return {
|
|
758
|
+
...basePayload,
|
|
759
|
+
message: params[1] as string,
|
|
760
|
+
...(response ? { signatureHash: response as string } : {}),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private async buildTransactionEventPayload(params: unknown[]) {
|
|
765
|
+
const { data, from, to, value } = params[0] as {
|
|
766
|
+
data: string;
|
|
767
|
+
from: string;
|
|
768
|
+
to: string;
|
|
769
|
+
value: string;
|
|
770
|
+
};
|
|
771
|
+
return {
|
|
772
|
+
chainId: this.currentChainId || (await this.getCurrentChainId()),
|
|
773
|
+
data,
|
|
774
|
+
address: from,
|
|
775
|
+
to,
|
|
776
|
+
value,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
private getActionDescriptor(action: string, payload: any): string {
|
|
781
|
+
return `${action}${payload.status ? ` ${payload.status}` : ""}`;
|
|
782
|
+
}
|
|
434
783
|
}
|