@formo/analytics 1.13.4 → 1.14.2
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/.github/workflows/ci.yml +51 -0
- package/CONTRIBUTING.md +1 -1
- package/dist/cjs/src/FormoAnalytics.d.ts +6 -5
- package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -1
- package/dist/cjs/src/FormoAnalytics.js +88 -83
- package/dist/cjs/src/FormoAnalytics.js.map +1 -1
- package/dist/cjs/src/FormoAnalyticsProvider.d.ts +4 -5
- package/dist/cjs/src/FormoAnalyticsProvider.d.ts.map +1 -1
- package/dist/cjs/src/FormoAnalyticsProvider.js +39 -62
- package/dist/cjs/src/FormoAnalyticsProvider.js.map +1 -1
- package/dist/cjs/src/constants/base.d.ts +3 -0
- package/dist/cjs/src/constants/base.d.ts.map +1 -0
- package/dist/cjs/src/constants/base.js +6 -0
- package/dist/cjs/src/constants/base.js.map +1 -0
- package/dist/cjs/src/constants/config.d.ts +1 -1
- package/dist/cjs/src/constants/config.js +58 -58
- package/dist/cjs/src/constants/config.js.map +1 -1
- package/dist/cjs/src/constants/index.d.ts +4 -2
- package/dist/cjs/src/constants/index.d.ts.map +1 -1
- package/dist/cjs/src/constants/index.js +2 -0
- package/dist/cjs/src/constants/index.js.map +1 -1
- package/dist/cjs/src/constants/regex.d.ts +4 -0
- package/dist/cjs/src/constants/regex.d.ts.map +1 -0
- package/dist/cjs/src/constants/regex.js +7 -0
- package/dist/cjs/src/constants/regex.js.map +1 -0
- package/dist/cjs/src/lib/fingerprint.d.ts +4 -0
- package/dist/cjs/src/lib/fingerprint.d.ts.map +1 -0
- package/dist/cjs/src/lib/fingerprint.js +63 -0
- package/dist/cjs/src/lib/fingerprint.js.map +1 -0
- package/dist/cjs/src/lib/index.d.ts +3 -0
- package/dist/cjs/src/lib/index.d.ts.map +1 -0
- package/dist/cjs/src/lib/index.js +24 -0
- package/dist/cjs/src/lib/index.js.map +1 -0
- package/dist/cjs/src/lib/queue.d.ts +33 -0
- package/dist/cjs/src/lib/queue.d.ts.map +1 -0
- package/dist/cjs/src/lib/queue.js +319 -0
- package/dist/cjs/src/lib/queue.js.map +1 -0
- package/dist/cjs/src/lib/session-storage.d.ts +11 -0
- package/dist/cjs/src/lib/session-storage.d.ts.map +1 -0
- package/dist/cjs/src/lib/session-storage.js +52 -0
- package/dist/cjs/src/lib/session-storage.js.map +1 -0
- package/dist/cjs/src/lib/utils.d.ts +6 -0
- package/dist/cjs/src/lib/utils.d.ts.map +1 -1
- package/dist/cjs/src/lib/utils.js +32 -0
- package/dist/cjs/src/lib/utils.js.map +1 -1
- package/dist/cjs/src/types/base.d.ts +8 -2
- package/dist/cjs/src/types/base.d.ts.map +1 -1
- package/dist/cjs/src/types/events.d.ts +7 -0
- package/dist/cjs/src/types/events.d.ts.map +1 -1
- package/dist/cjs/src/types/events.js.map +1 -1
- package/dist/cjs/test/lib.spec.js +11 -3
- package/dist/cjs/test/lib.spec.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/src/FormoAnalytics.d.ts +6 -5
- package/dist/esm/src/FormoAnalytics.d.ts.map +1 -1
- package/dist/esm/src/FormoAnalytics.js +87 -79
- package/dist/esm/src/FormoAnalytics.js.map +1 -1
- package/dist/esm/src/FormoAnalyticsProvider.d.ts +4 -5
- package/dist/esm/src/FormoAnalyticsProvider.d.ts.map +1 -1
- package/dist/esm/src/FormoAnalyticsProvider.js +40 -63
- package/dist/esm/src/FormoAnalyticsProvider.js.map +1 -1
- package/dist/esm/src/constants/base.d.ts +3 -0
- package/dist/esm/src/constants/base.d.ts.map +1 -0
- package/dist/esm/src/constants/base.js +3 -0
- package/dist/esm/src/constants/base.js.map +1 -0
- package/dist/esm/src/constants/config.d.ts +1 -1
- package/dist/esm/src/constants/config.js +58 -58
- package/dist/esm/src/constants/config.js.map +1 -1
- package/dist/esm/src/constants/index.d.ts +4 -2
- package/dist/esm/src/constants/index.d.ts.map +1 -1
- package/dist/esm/src/constants/index.js +4 -2
- package/dist/esm/src/constants/index.js.map +1 -1
- package/dist/esm/src/constants/regex.d.ts +4 -0
- package/dist/esm/src/constants/regex.d.ts.map +1 -0
- package/dist/esm/src/constants/regex.js +4 -0
- package/dist/esm/src/constants/regex.js.map +1 -0
- package/dist/esm/src/lib/fingerprint.d.ts +4 -0
- package/dist/esm/src/lib/fingerprint.d.ts.map +1 -0
- package/dist/esm/src/lib/fingerprint.js +60 -0
- package/dist/esm/src/lib/fingerprint.js.map +1 -0
- package/dist/esm/src/lib/index.d.ts +3 -0
- package/dist/esm/src/lib/index.d.ts.map +1 -0
- package/dist/esm/src/lib/index.js +3 -0
- package/dist/esm/src/lib/index.js.map +1 -0
- package/dist/esm/src/lib/queue.d.ts +33 -0
- package/dist/esm/src/lib/queue.d.ts.map +1 -0
- package/dist/esm/src/lib/queue.js +313 -0
- package/dist/esm/src/lib/queue.js.map +1 -0
- package/dist/esm/src/lib/session-storage.d.ts +11 -0
- package/dist/esm/src/lib/session-storage.d.ts.map +1 -0
- package/dist/esm/src/lib/session-storage.js +49 -0
- package/dist/esm/src/lib/session-storage.js.map +1 -0
- package/dist/esm/src/lib/utils.d.ts +6 -0
- package/dist/esm/src/lib/utils.d.ts.map +1 -1
- package/dist/esm/src/lib/utils.js +25 -0
- package/dist/esm/src/lib/utils.js.map +1 -1
- package/dist/esm/src/types/base.d.ts +8 -2
- package/dist/esm/src/types/base.d.ts.map +1 -1
- package/dist/esm/src/types/events.d.ts +7 -0
- package/dist/esm/src/types/events.d.ts.map +1 -1
- package/dist/esm/src/types/events.js.map +1 -1
- package/dist/esm/test/lib.spec.js +9 -1
- package/dist/esm/test/lib.spec.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.LICENSE.txt +0 -12
- package/dist/index.umd.min.js.map +1 -1
- package/package.json +7 -6
- package/src/FormoAnalytics.ts +86 -66
- package/src/FormoAnalyticsProvider.tsx +32 -67
- package/src/constants/base.ts +2 -0
- package/src/constants/config.ts +58 -58
- package/src/constants/index.ts +4 -2
- package/src/constants/regex.ts +3 -0
- package/src/lib/fingerprint.ts +9 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/queue.ts +310 -0
- package/src/lib/session-storage.ts +53 -0
- package/src/lib/utils.ts +31 -0
- package/src/types/base.ts +12 -5
- package/src/types/events.ts +15 -7
- package/test/lib.spec.ts +13 -1
- package/dist/387.index.umd.min.js +0 -125
- package/dist/387.index.umd.min.js.map +0 -1
package/src/constants/config.ts
CHANGED
|
@@ -64,7 +64,7 @@ export const COUNTRY_LIST = {
|
|
|
64
64
|
"America/Araguaina": "BR",
|
|
65
65
|
"America/Argentina/Buenos_Aires": "AR",
|
|
66
66
|
"America/Argentina/Catamarca": "AR",
|
|
67
|
-
"America/Argentina/
|
|
67
|
+
"America/Argentina/ComodRivadavia": "AR",
|
|
68
68
|
"America/Argentina/Cordoba": "AR",
|
|
69
69
|
"America/Argentina/Jujuy": "AR",
|
|
70
70
|
"America/Argentina/La_Rioja": "AR",
|
|
@@ -181,7 +181,7 @@ export const COUNTRY_LIST = {
|
|
|
181
181
|
"America/North_Dakota/New_Salem": "US",
|
|
182
182
|
"America/Nuuk": "GL",
|
|
183
183
|
"America/Ojinaga": "MX",
|
|
184
|
-
"America/Panama": "PA
|
|
184
|
+
"America/Panama": "PA",
|
|
185
185
|
"America/Pangnirtung": "CA",
|
|
186
186
|
"America/Paramaribo": "SR",
|
|
187
187
|
"America/Phoenix": "US,CA",
|
|
@@ -392,58 +392,58 @@ export const COUNTRY_LIST = {
|
|
|
392
392
|
"Canada/Saskatchewan": "CA",
|
|
393
393
|
"Canada/Yukon": "CA",
|
|
394
394
|
// CET
|
|
395
|
-
CET: "",
|
|
395
|
+
CET: "CET",
|
|
396
396
|
// Chile
|
|
397
397
|
"Chile/Continental": "CL",
|
|
398
398
|
"Chile/EasterIsland": "CL",
|
|
399
399
|
// CST6CDT
|
|
400
|
-
CST6CDT: "",
|
|
400
|
+
CST6CDT: "CST6CDT",
|
|
401
401
|
// Cuba
|
|
402
402
|
Cuba: "CU",
|
|
403
403
|
// EET
|
|
404
|
-
EET: "",
|
|
404
|
+
EET: "EET",
|
|
405
405
|
// Egypt
|
|
406
406
|
Egypt: "EG",
|
|
407
407
|
// Eire
|
|
408
408
|
Eire: "IE",
|
|
409
409
|
// EST
|
|
410
|
-
EST: "",
|
|
411
|
-
EST5EDT: "",
|
|
412
|
-
"Etc/GMT": "",
|
|
413
|
-
"Etc/GMT+0": "",
|
|
414
|
-
"Etc/GMT+1": "",
|
|
415
|
-
"Etc/GMT+10": "",
|
|
416
|
-
"Etc/GMT+11": "",
|
|
417
|
-
"Etc/GMT+12": "",
|
|
418
|
-
"Etc/GMT+2": "",
|
|
419
|
-
"Etc/GMT+3": "",
|
|
420
|
-
"Etc/GMT+4": "",
|
|
421
|
-
"Etc/GMT+5": "",
|
|
422
|
-
"Etc/GMT+6": "",
|
|
423
|
-
"Etc/GMT+7": "",
|
|
424
|
-
"Etc/GMT+8": "",
|
|
425
|
-
"Etc/GMT+9": "",
|
|
426
|
-
"Etc/GMT-0": "",
|
|
427
|
-
"Etc/GMT-1": "",
|
|
428
|
-
"Etc/GMT-10": "",
|
|
429
|
-
"Etc/GMT-11": "",
|
|
430
|
-
"Etc/GMT-12": "",
|
|
431
|
-
"Etc/GMT-13": "",
|
|
432
|
-
"Etc/GMT-14": "",
|
|
433
|
-
"Etc/GMT-2": "",
|
|
434
|
-
"Etc/GMT-3": "",
|
|
435
|
-
"Etc/GMT-4": "",
|
|
436
|
-
"Etc/GMT-5": "",
|
|
437
|
-
"Etc/GMT-6": "",
|
|
438
|
-
"Etc/GMT-7": "",
|
|
439
|
-
"Etc/GMT-8": "",
|
|
440
|
-
"Etc/GMT-9": "",
|
|
441
|
-
"Etc/GMT0": "",
|
|
442
|
-
"Etc/Greenwich": "",
|
|
443
|
-
"Etc/UCT": "",
|
|
444
|
-
"Etc/UTC": "",
|
|
445
|
-
"Etc/Universal": "",
|
|
446
|
-
"Etc/Zulu": "",
|
|
410
|
+
EST: "EST",
|
|
411
|
+
EST5EDT: "EST5EDT",
|
|
412
|
+
"Etc/GMT": "Etc/GMT",
|
|
413
|
+
"Etc/GMT+0": "Etc/GMT+0",
|
|
414
|
+
"Etc/GMT+1": "Etc/GMT+1",
|
|
415
|
+
"Etc/GMT+10": "Etc/GMT+10",
|
|
416
|
+
"Etc/GMT+11": "Etc/GMT+11",
|
|
417
|
+
"Etc/GMT+12": "Etc/GMT+12",
|
|
418
|
+
"Etc/GMT+2": "Etc/GMT+2",
|
|
419
|
+
"Etc/GMT+3": "Etc/GMT+3",
|
|
420
|
+
"Etc/GMT+4": "Etc/GMT+4",
|
|
421
|
+
"Etc/GMT+5": "Etc/GMT+5",
|
|
422
|
+
"Etc/GMT+6": "Etc/GMT+6",
|
|
423
|
+
"Etc/GMT+7": "Etc/GMT+7",
|
|
424
|
+
"Etc/GMT+8": "Etc/GMT+8",
|
|
425
|
+
"Etc/GMT+9": "Etc/GMT+9",
|
|
426
|
+
"Etc/GMT-0": "Etc/GMT-0",
|
|
427
|
+
"Etc/GMT-1": "Etc/GMT-1",
|
|
428
|
+
"Etc/GMT-10": "Etc/GMT-10",
|
|
429
|
+
"Etc/GMT-11": "Etc/GMT-11",
|
|
430
|
+
"Etc/GMT-12": "Etc/GMT-12",
|
|
431
|
+
"Etc/GMT-13": "Etc/GMT-13",
|
|
432
|
+
"Etc/GMT-14": "Etc/GMT-14",
|
|
433
|
+
"Etc/GMT-2": "Etc/GMT-2",
|
|
434
|
+
"Etc/GMT-3": "Etc/GMT-3",
|
|
435
|
+
"Etc/GMT-4": "Etc/GMT-4",
|
|
436
|
+
"Etc/GMT-5": "Etc/GMT-5",
|
|
437
|
+
"Etc/GMT-6": "Etc/GMT-6",
|
|
438
|
+
"Etc/GMT-7": "Etc/GMT-7",
|
|
439
|
+
"Etc/GMT-8": "Etc/GMT-8",
|
|
440
|
+
"Etc/GMT-9": "Etc/GMT-9",
|
|
441
|
+
"Etc/GMT0": "Etc/GMT0",
|
|
442
|
+
"Etc/Greenwich": "Etc/Greenwich",
|
|
443
|
+
"Etc/UCT": "Etc/UCT",
|
|
444
|
+
"Etc/UTC": "Etc/UTC",
|
|
445
|
+
"Etc/Universal": "Etc/Universal",
|
|
446
|
+
"Etc/Zulu": "Etc/Zulu",
|
|
447
447
|
// Europe
|
|
448
448
|
"Europe/Amsterdam": "NL",
|
|
449
449
|
"Europe/Andorra": "AD",
|
|
@@ -510,20 +510,20 @@ export const COUNTRY_LIST = {
|
|
|
510
510
|
"Europe/Zaporozhye": "UA",
|
|
511
511
|
"Europe/Zurich": "CH",
|
|
512
512
|
// Factory
|
|
513
|
-
Factory: "",
|
|
513
|
+
Factory: "Factory",
|
|
514
514
|
// GB
|
|
515
515
|
GB: "GB",
|
|
516
516
|
"GB-Eire": "GB",
|
|
517
517
|
// GMT
|
|
518
|
-
GMT: "",
|
|
519
|
-
"GMT+0": "",
|
|
520
|
-
"GMT-0": "",
|
|
521
|
-
GMT0: "",
|
|
522
|
-
Greenwich: "",
|
|
518
|
+
GMT: "GMT",
|
|
519
|
+
"GMT+0": "GMT+0",
|
|
520
|
+
"GMT-0": "GMT-0",
|
|
521
|
+
GMT0: "GMT0",
|
|
522
|
+
Greenwich: "Greenwich",
|
|
523
523
|
// HK
|
|
524
524
|
Hongkong: "HK",
|
|
525
525
|
// HST
|
|
526
|
-
HST: "",
|
|
526
|
+
HST: "HST",
|
|
527
527
|
// Iceland
|
|
528
528
|
Iceland: "IS",
|
|
529
529
|
// Indian
|
|
@@ -551,14 +551,14 @@ export const COUNTRY_LIST = {
|
|
|
551
551
|
// Libya
|
|
552
552
|
Libya: "LY",
|
|
553
553
|
// MET
|
|
554
|
-
MET: "",
|
|
554
|
+
MET: "MET",
|
|
555
555
|
// Mexico
|
|
556
556
|
"Mexico/BajaNorte": "MX",
|
|
557
557
|
"Mexico/BajaSur": "MX",
|
|
558
558
|
"Mexico/General": "MX",
|
|
559
559
|
// MST
|
|
560
|
-
MST: "",
|
|
561
|
-
MST7MDT: "",
|
|
560
|
+
MST: "MST",
|
|
561
|
+
MST7MDT: "MST7MDT",
|
|
562
562
|
// Navajo
|
|
563
563
|
Navajo: "US",
|
|
564
564
|
// NZ
|
|
@@ -616,7 +616,7 @@ export const COUNTRY_LIST = {
|
|
|
616
616
|
// PRC
|
|
617
617
|
PRC: "CN",
|
|
618
618
|
// PST8PDT
|
|
619
|
-
PST8PDT: "",
|
|
619
|
+
PST8PDT: "PST8PDT",
|
|
620
620
|
// ROC
|
|
621
621
|
ROC: "TW",
|
|
622
622
|
// ROK
|
|
@@ -626,9 +626,9 @@ export const COUNTRY_LIST = {
|
|
|
626
626
|
// Turkey
|
|
627
627
|
Turkey: "TR",
|
|
628
628
|
// UCT
|
|
629
|
-
UCT: "",
|
|
629
|
+
UCT: "UCT",
|
|
630
630
|
// Universal
|
|
631
|
-
Universal: "",
|
|
631
|
+
Universal: "Universal",
|
|
632
632
|
// US
|
|
633
633
|
"US/Alaska": "US",
|
|
634
634
|
"US/Aleutian": "US",
|
|
@@ -643,11 +643,11 @@ export const COUNTRY_LIST = {
|
|
|
643
643
|
"US/Pacific": "US",
|
|
644
644
|
"US/Samoa": "AS",
|
|
645
645
|
// UTC
|
|
646
|
-
UTC: "",
|
|
646
|
+
UTC: "UTC",
|
|
647
647
|
// W-SU
|
|
648
648
|
"W-SU": "RU",
|
|
649
649
|
// WET
|
|
650
|
-
WET: "",
|
|
650
|
+
WET: "WET",
|
|
651
651
|
// Zulu
|
|
652
|
-
Zulu: "",
|
|
652
|
+
Zulu: "Zulu",
|
|
653
653
|
};
|
package/src/constants/index.ts
CHANGED
package/src/lib/index.ts
ADDED
package/src/lib/queue.ts
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import fetch from "fetch-retry";
|
|
2
|
+
import isNetworkError from "is-network-error";
|
|
3
|
+
import { RequestEvent } from "../types";
|
|
4
|
+
import {
|
|
5
|
+
clampNumber,
|
|
6
|
+
getActionDescriptor,
|
|
7
|
+
millisecondsToSecond,
|
|
8
|
+
toDateHourMinute,
|
|
9
|
+
} from "./utils";
|
|
10
|
+
import { Fingerprint } from "./fingerprint";
|
|
11
|
+
|
|
12
|
+
const sdkFetch = fetch(global.fetch);
|
|
13
|
+
|
|
14
|
+
const noop = () => {};
|
|
15
|
+
|
|
16
|
+
type QueueItem = {
|
|
17
|
+
message: RequestEvent;
|
|
18
|
+
callback: (...args: any) => any;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type Options = {
|
|
22
|
+
url: string;
|
|
23
|
+
flushAt?: number;
|
|
24
|
+
flushInterval?: number;
|
|
25
|
+
host?: string;
|
|
26
|
+
retryCount?: number;
|
|
27
|
+
errorHandler?: any;
|
|
28
|
+
maxQueueSize?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const DEFAULT_RETRY = 3;
|
|
32
|
+
const MAX_RETRY = 5;
|
|
33
|
+
const MIN_RETRY = 1;
|
|
34
|
+
|
|
35
|
+
const DEFAULT_FLUSH_AT = 20;
|
|
36
|
+
const MAX_FLUSH_AT = 20;
|
|
37
|
+
const MIN_FLUSH_AT = 1;
|
|
38
|
+
|
|
39
|
+
const DEFAULT_QUEUE_SIZE = 1_024 * 500; // 500kB
|
|
40
|
+
const MAX_QUEUE_SIZE = 1_024 * 500; // 500kB
|
|
41
|
+
const MIN_QUEUE_SIZE = 200; // 200 bytes
|
|
42
|
+
|
|
43
|
+
const DEFAULT_FLUSH_INTERVAL = 1_000 * 30; // 1 MINUTE
|
|
44
|
+
const MAX_FLUSH_INTERVAL = 1_000 * 300; // 5 MINUTES
|
|
45
|
+
const MIN_FLUSH_INTERVAL = 1_000 * 10; // 10 SECONDS
|
|
46
|
+
|
|
47
|
+
export class EventQueue {
|
|
48
|
+
private writeKey: string;
|
|
49
|
+
private url: string;
|
|
50
|
+
private queue: QueueItem[];
|
|
51
|
+
private timer: null | NodeJS.Timeout;
|
|
52
|
+
private flushAt: number;
|
|
53
|
+
private flushInterval: number;
|
|
54
|
+
private flushed: boolean;
|
|
55
|
+
private maxQueueSize: number; // min 200 bytes, max 500kB
|
|
56
|
+
private errorHandler: any;
|
|
57
|
+
private retryCount: number;
|
|
58
|
+
private pendingFlush: Promise<any> | null;
|
|
59
|
+
private payloadHashes: Set<string> = new Set();
|
|
60
|
+
|
|
61
|
+
constructor(writeKey: string, options: Options) {
|
|
62
|
+
options = options || {};
|
|
63
|
+
|
|
64
|
+
this.queue = [];
|
|
65
|
+
this.writeKey = writeKey;
|
|
66
|
+
this.url = options.url;
|
|
67
|
+
this.retryCount = clampNumber(
|
|
68
|
+
options.retryCount || DEFAULT_RETRY,
|
|
69
|
+
MAX_RETRY,
|
|
70
|
+
MIN_RETRY
|
|
71
|
+
);
|
|
72
|
+
this.flushAt = clampNumber(
|
|
73
|
+
options.flushAt || DEFAULT_FLUSH_AT,
|
|
74
|
+
MAX_FLUSH_AT,
|
|
75
|
+
MIN_FLUSH_AT
|
|
76
|
+
);
|
|
77
|
+
this.maxQueueSize = clampNumber(
|
|
78
|
+
options.maxQueueSize || DEFAULT_QUEUE_SIZE,
|
|
79
|
+
MAX_QUEUE_SIZE,
|
|
80
|
+
MIN_QUEUE_SIZE
|
|
81
|
+
);
|
|
82
|
+
this.flushInterval = clampNumber(
|
|
83
|
+
options.flushInterval || DEFAULT_FLUSH_INTERVAL,
|
|
84
|
+
MAX_FLUSH_INTERVAL,
|
|
85
|
+
MIN_FLUSH_INTERVAL
|
|
86
|
+
);
|
|
87
|
+
this.flushed = true;
|
|
88
|
+
this.errorHandler = options.errorHandler;
|
|
89
|
+
this.pendingFlush = null;
|
|
90
|
+
this.timer = null;
|
|
91
|
+
|
|
92
|
+
this.onPageLeave(async (isAccessible: boolean) => {
|
|
93
|
+
if (isAccessible === false) {
|
|
94
|
+
await this.flush();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#region Public functions
|
|
100
|
+
async enqueue(message: RequestEvent, callback?: (...args: any) => void) {
|
|
101
|
+
callback = callback || noop;
|
|
102
|
+
|
|
103
|
+
// check if the message already exists
|
|
104
|
+
if (await this.checkDuplicate(message)) {
|
|
105
|
+
console.warn(
|
|
106
|
+
`Event already enqueued, try again after ${millisecondsToSecond(
|
|
107
|
+
this.flushInterval
|
|
108
|
+
)} seconds.`
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.queue.push({ message, callback });
|
|
114
|
+
|
|
115
|
+
console.log(
|
|
116
|
+
`Event enqueued: ${getActionDescriptor(message.action, message.payload)}`
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!this.flushed) {
|
|
120
|
+
this.flushed = true;
|
|
121
|
+
this.flush();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const hasReachedFlushAt = this.queue.length >= this.flushAt;
|
|
126
|
+
const hasReachedQueueSize =
|
|
127
|
+
this.queue.reduce((acc, item) => acc + JSON.stringify(item).length, 0) >=
|
|
128
|
+
this.maxQueueSize;
|
|
129
|
+
|
|
130
|
+
if (hasReachedFlushAt || hasReachedQueueSize) {
|
|
131
|
+
this.flush();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.flushInterval && !this.timer) {
|
|
136
|
+
this.timer = setTimeout(this.flush.bind(this), this.flushInterval);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async flush(callback?: (...args: any) => void) {
|
|
141
|
+
callback = callback || noop;
|
|
142
|
+
|
|
143
|
+
if (this.timer) {
|
|
144
|
+
clearTimeout(this.timer);
|
|
145
|
+
this.timer = null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!this.queue.length) {
|
|
149
|
+
callback();
|
|
150
|
+
return Promise.resolve();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
if (this.pendingFlush) {
|
|
155
|
+
await this.pendingFlush;
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
this.pendingFlush = null;
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const items = this.queue.splice(0, this.flushAt);
|
|
163
|
+
this.payloadHashes.clear();
|
|
164
|
+
const data = items.map((item) => item.message);
|
|
165
|
+
|
|
166
|
+
const done = (err?: Error) => {
|
|
167
|
+
items.forEach(({ message, callback }) => callback(err, message, data));
|
|
168
|
+
callback(err, data);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const req = {
|
|
172
|
+
headers: {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
Authorization: `Basic ${this.writeKey}`,
|
|
175
|
+
"X-Visitor-Id": await Fingerprint.getVisitorId(),
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return (this.pendingFlush = sdkFetch(`${this.url}`, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
body: JSON.stringify(data),
|
|
182
|
+
keepalive: true,
|
|
183
|
+
retries: this.retryCount,
|
|
184
|
+
retryDelay: (attempt) => Math.pow(2, attempt) * 1_000, // exponential backoff
|
|
185
|
+
retryOn: (_, error) => this.isErrorRetryable(error),
|
|
186
|
+
...req,
|
|
187
|
+
})
|
|
188
|
+
.then(() => {
|
|
189
|
+
done();
|
|
190
|
+
return Promise.resolve(data);
|
|
191
|
+
})
|
|
192
|
+
.catch((err) => {
|
|
193
|
+
if (typeof this.errorHandler === "function") {
|
|
194
|
+
done(err);
|
|
195
|
+
return this.errorHandler(err);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (err.response) {
|
|
199
|
+
const error = new Error(err.response.statusText);
|
|
200
|
+
done(error);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
done(err);
|
|
205
|
+
throw err;
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//#region Utility functions
|
|
210
|
+
private isErrorRetryable(error: any) {
|
|
211
|
+
// Retry Network Errors.
|
|
212
|
+
if (isNetworkError(error)) return true;
|
|
213
|
+
|
|
214
|
+
// Cannot determine if the request can be retried
|
|
215
|
+
if (!error?.response) return false;
|
|
216
|
+
|
|
217
|
+
// Retry Server Errors (5xx).
|
|
218
|
+
if (error?.response?.status >= 500 && error?.response?.status <= 599)
|
|
219
|
+
return true;
|
|
220
|
+
|
|
221
|
+
// Retry if rate limited.
|
|
222
|
+
if (error?.response?.status === 429) return true;
|
|
223
|
+
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private async checkDuplicate(newMessage: RequestEvent) {
|
|
228
|
+
// check if exists a message with identical payload within 1 minute
|
|
229
|
+
const formattedTimestamp = toDateHourMinute(new Date(newMessage.timestamp));
|
|
230
|
+
newMessage.timestamp = formattedTimestamp;
|
|
231
|
+
|
|
232
|
+
const hash = await this.hashPayload(newMessage);
|
|
233
|
+
if (this.payloadHashes.has(hash)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
this.payloadHashes.add(hash);
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private async hashPayload(payload: RequestEvent, algo = "SHA-1") {
|
|
241
|
+
return Array.from(
|
|
242
|
+
new Uint8Array(
|
|
243
|
+
await crypto.subtle.digest(
|
|
244
|
+
algo,
|
|
245
|
+
new TextEncoder().encode(JSON.stringify(payload))
|
|
246
|
+
)
|
|
247
|
+
),
|
|
248
|
+
(byte) => byte.toString(16).padStart(2, "0")
|
|
249
|
+
).join("");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private onPageLeave = (callback: (isAccessible: boolean) => void) => {
|
|
253
|
+
// To ensure the callback is only called once even if more than one events
|
|
254
|
+
// are fired at once.
|
|
255
|
+
let pageLeft = false;
|
|
256
|
+
let isAccessible = false;
|
|
257
|
+
|
|
258
|
+
function handleOnLeave() {
|
|
259
|
+
if (pageLeft) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
pageLeft = true;
|
|
264
|
+
|
|
265
|
+
callback(isAccessible);
|
|
266
|
+
|
|
267
|
+
// Reset pageLeft on the next tick
|
|
268
|
+
// to ensure callback executes for other listeners
|
|
269
|
+
// when closing an inactive browser tab.
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
pageLeft = false;
|
|
272
|
+
}, 0);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Catches the unloading of the page (e.g., closing the tab or navigating away).
|
|
276
|
+
// Includes user actions like clicking a link, entering a new URL,
|
|
277
|
+
// refreshing the page, or closing the browser tab
|
|
278
|
+
// Note that 'pagehide' is not supported in IE.
|
|
279
|
+
// So, this is a fallback.
|
|
280
|
+
(globalThis as typeof window).addEventListener("beforeunload", () => {
|
|
281
|
+
isAccessible = false;
|
|
282
|
+
handleOnLeave();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
(globalThis as typeof window).addEventListener("blur", () => {
|
|
286
|
+
isAccessible = true;
|
|
287
|
+
handleOnLeave();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
(globalThis as typeof window).addEventListener("focus", () => {
|
|
291
|
+
pageLeft = false;
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Catches the page being hidden, including scenarios like closing the tab.
|
|
295
|
+
document.addEventListener("pagehide", () => {
|
|
296
|
+
isAccessible = document.visibilityState === "hidden";
|
|
297
|
+
handleOnLeave();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Catches visibility changes, such as switching tabs or minimizing the browser.
|
|
301
|
+
document.addEventListener("visibilitychange", () => {
|
|
302
|
+
isAccessible = true;
|
|
303
|
+
if (document.visibilityState === "hidden") {
|
|
304
|
+
handleOnLeave();
|
|
305
|
+
} else {
|
|
306
|
+
pageLeft = false;
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class SessionStorage {
|
|
2
|
+
private readonly json_prefix = "__json=";
|
|
3
|
+
|
|
4
|
+
public set(key: string, value: any): void {
|
|
5
|
+
if (typeof value === "boolean") value = value === true ? "true" : "false";
|
|
6
|
+
if (typeof value === "object")
|
|
7
|
+
value = this.json_prefix + JSON.stringify(value);
|
|
8
|
+
sessionStorage.setItem(key, value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public get(key: string): string | boolean | Record<any, any> | null {
|
|
12
|
+
const value = sessionStorage.getItem(key);
|
|
13
|
+
|
|
14
|
+
if (!value || typeof value !== "string") return null;
|
|
15
|
+
if (["null", "undefined"].some((item) => item == value)) return null;
|
|
16
|
+
|
|
17
|
+
if (value.startsWith(this.json_prefix)) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(value.slice(7));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(
|
|
22
|
+
"[FORMO_ERROR] SessionStorage failed to parse JSON",
|
|
23
|
+
error
|
|
24
|
+
);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (["true", "false"].some((item) => item == value)) {
|
|
30
|
+
return JSON.parse(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public removeMatch(pattern: RegExp): void {
|
|
37
|
+
for (const key in sessionStorage) {
|
|
38
|
+
if (pattern.test(key)) {
|
|
39
|
+
this.remove(key);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public remove(key: string): void {
|
|
45
|
+
sessionStorage.removeItem(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public clear(): void {
|
|
49
|
+
sessionStorage.clear();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default new SessionStorage();
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { REGEX } from "../constants";
|
|
2
|
+
|
|
1
3
|
const toSnake = (str: string) =>
|
|
2
4
|
str
|
|
3
5
|
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
@@ -24,3 +26,32 @@ export function toSnakeCase(obj: any, omitKeys: string[] = []) {
|
|
|
24
26
|
|
|
25
27
|
return convert(obj);
|
|
26
28
|
}
|
|
29
|
+
|
|
30
|
+
export const isLocalhost = () =>
|
|
31
|
+
/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*:)*?:?0*1$/.test(
|
|
32
|
+
window.location.hostname
|
|
33
|
+
) || window.location.protocol === "file:";
|
|
34
|
+
|
|
35
|
+
export const isAddress = (address: string) => REGEX.addressRegex.test(address);
|
|
36
|
+
|
|
37
|
+
export const millisecondsToSecond = (milliseconds: number): number =>
|
|
38
|
+
Math.ceil(milliseconds / 1_000);
|
|
39
|
+
|
|
40
|
+
export const toDateHourMinute = (date: Date) =>
|
|
41
|
+
date.getUTCFullYear() +
|
|
42
|
+
"-" +
|
|
43
|
+
("0" + (date.getUTCMonth() + 1)).slice(-2) +
|
|
44
|
+
"-" +
|
|
45
|
+
("0" + date.getUTCDate()).slice(-2) +
|
|
46
|
+
" " +
|
|
47
|
+
("0" + date.getUTCHours()).slice(-2) +
|
|
48
|
+
":" +
|
|
49
|
+
("0" + date.getUTCMinutes()).slice(-2);
|
|
50
|
+
|
|
51
|
+
export const clampNumber = (value: number, max: number, min: number) => {
|
|
52
|
+
return Math.min(Math.max(value, min), max);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const getActionDescriptor = (action: string, payload: any): string => {
|
|
56
|
+
return `${action}${payload?.status ? ` ${payload?.status}` : ""}`;
|
|
57
|
+
};
|
package/src/types/base.ts
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import { EIP1193Provider } from "./provider";
|
|
2
2
|
|
|
3
3
|
// Decimal chain ID
|
|
4
|
-
export type ChainID = number
|
|
4
|
+
export type ChainID = number;
|
|
5
5
|
|
|
6
6
|
// Address (EVM, Solana, etc.)
|
|
7
|
-
export type Address = string
|
|
7
|
+
export type Address = string;
|
|
8
8
|
|
|
9
9
|
export interface Options {
|
|
10
10
|
provider?: EIP1193Provider;
|
|
11
|
+
trackLocalhost?: boolean;
|
|
12
|
+
|
|
13
|
+
flushAt?: number;
|
|
14
|
+
flushInterval?: number;
|
|
15
|
+
retryCount?: number;
|
|
16
|
+
maxQueueSize?: number;
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
export interface FormoAnalyticsProviderProps {
|
|
14
|
-
|
|
20
|
+
writeKey: string;
|
|
15
21
|
options?: Options;
|
|
16
22
|
disabled?: boolean;
|
|
17
23
|
children: React.ReactNode;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
export interface Config {
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
writeKey: string;
|
|
28
|
+
trackLocalhost?: boolean;
|
|
29
|
+
}
|
package/src/types/events.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
export interface RequestEvent {
|
|
2
|
+
action: string;
|
|
3
|
+
payload: Record<string, unknown>;
|
|
4
|
+
address: string | null;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
version: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
export enum SignatureStatus {
|
|
2
|
-
REQUESTED =
|
|
3
|
-
REJECTED =
|
|
4
|
-
CONFIRMED =
|
|
10
|
+
REQUESTED = "requested",
|
|
11
|
+
REJECTED = "rejected",
|
|
12
|
+
CONFIRMED = "confirmed",
|
|
5
13
|
}
|
|
6
14
|
|
|
7
15
|
export enum TransactionStatus {
|
|
8
|
-
STARTED =
|
|
9
|
-
REJECTED =
|
|
10
|
-
BROADCASTED =
|
|
11
|
-
}
|
|
16
|
+
STARTED = "started",
|
|
17
|
+
REJECTED = "rejected",
|
|
18
|
+
BROADCASTED = "broadcasted",
|
|
19
|
+
}
|