@reservamos/browser-analytics 0.1.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.
@@ -0,0 +1,131 @@
1
+ import { FpjsClient } from '@fingerprintjs/fingerprintjs-pro-spa';
2
+
3
+ declare global {
4
+ interface Window {
5
+ fpClient: FpjsClient;
6
+ fingerprintConfig: {
7
+ cacheName?: string;
8
+ cacheTimeInDays?: number;
9
+ };
10
+ }
11
+ }
12
+
13
+ const DEFAULT_CACHE_NAME = 'default_fingerprint_cache';
14
+ const DEFAULT_CACHE_TIME_IN_DAYS = 7;
15
+
16
+ /**
17
+ * Calculates the expiration date based on the cache time.
18
+ * @param {number} cacheTimeInDays - The cache time in days.
19
+ * @returns {number} - The expiration date in milliseconds since the epoch.
20
+ */
21
+ const getExpirationDate = (cacheTimeInDays: number): number => {
22
+ const currentDate = new Date();
23
+ const futureDate = new Date(
24
+ currentDate.getTime() + cacheTimeInDays * 24 * 60 * 60 * 1000,
25
+ );
26
+ return futureDate.getTime();
27
+ };
28
+
29
+ /**
30
+ * Retrieves the cached fingerprint from local storage.
31
+ * @returns {string | null} - The cached fingerprint or null if it is expired or not found.
32
+ */
33
+ const getCachedFingerprint = (): string | null => {
34
+ const { cacheName = DEFAULT_CACHE_NAME } = window.fingerprintConfig;
35
+
36
+ try {
37
+ const cachedData = JSON.parse(localStorage.getItem(cacheName) || 'null');
38
+ if (cachedData && cachedData.expiresAt > Date.now()) {
39
+ return cachedData.fingerprint;
40
+ }
41
+ localStorage.removeItem(cacheName);
42
+ return null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Sets the cached fingerprint in local storage.
50
+ * @param {string} fingerprint - The fingerprint to be cached.
51
+ */
52
+ const setCachedFingerprint = (fingerprint: string): void => {
53
+ const {
54
+ cacheName = DEFAULT_CACHE_NAME,
55
+ cacheTimeInDays = DEFAULT_CACHE_TIME_IN_DAYS,
56
+ } = window.fingerprintConfig;
57
+
58
+ const cacheData = {
59
+ fingerprint,
60
+ expiresAt: getExpirationDate(cacheTimeInDays),
61
+ };
62
+ localStorage.setItem(cacheName, JSON.stringify(cacheData));
63
+ };
64
+
65
+ /**
66
+ * Initializes identification service with the provided API key and stores the instance in the window object.
67
+ * @param {string} apiKey - The API key for Fingerprint.js.
68
+ * @returns {Promise<void>} A promise that resolves when initialization is complete.
69
+ * @throws {Error} Throws an error if initialization fails.
70
+ */
71
+ export async function initFingerprint(apiKey: string): Promise<void> {
72
+ window.fingerprintConfig = {};
73
+ window.fpClient = new FpjsClient({
74
+ loadOptions: {
75
+ apiKey,
76
+ },
77
+ });
78
+
79
+ try {
80
+ await window.fpClient.init();
81
+ } catch (error) {
82
+ console.error('Error initializing identification service:', error);
83
+ throw error;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Retrieves the fingerprint, first checking the cache and then querying the identification service if not found.
89
+ * @param {boolean} cacheOnly - Optional flag to retrieve the fingerprint only from the cache.
90
+ * @returns {Promise<string>} A promise that resolves with the fingerprint.
91
+ * @throws {Error} Throws an error if retrieval fails.
92
+ */
93
+ export async function getFingerprint(cacheOnly = false): Promise<string> {
94
+ if (!window.fingerprintConfig) {
95
+ throw new Error('Fingerprint configuration is not initialized.');
96
+ }
97
+
98
+ const cachedFingerprint = getCachedFingerprint();
99
+ if (cachedFingerprint) {
100
+ return cachedFingerprint;
101
+ }
102
+
103
+ if (cacheOnly) {
104
+ return '';
105
+ }
106
+
107
+ if (!isFingerprintReady()) {
108
+ throw new Error('Identification service is not initialized.');
109
+ }
110
+
111
+ try {
112
+ const fpClient = window.fpClient;
113
+ const { visitorId: fingerprint } = await fpClient.getVisitorData();
114
+ setCachedFingerprint(fingerprint);
115
+ return fingerprint;
116
+ } catch (error) {
117
+ console.error('Error retrieving fingerprint:', error);
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Checks if the identification service is ready.
124
+ *
125
+ * This function verifies whether the identification service has been initialized
126
+ * and is ready to use by checking if the `fpClient` instance exists on the window object.
127
+ * @returns {boolean} Returns true if the identification service is ready, otherwise false.
128
+ */
129
+ export function isFingerprintReady(): boolean {
130
+ return window.fpClient !== undefined;
131
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { trackTest } from './events/trackTest';
2
+ import { init } from './init';
3
+
4
+ export { init } from './init';
5
+ export { trackTest } from './events/trackTest';
6
+
7
+ const tracker = {
8
+ init,
9
+ trackTest,
10
+ };
11
+
12
+ export default tracker;
package/src/init.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { initFingerprint } from './fingerprint'; // Import the new fingerprint initialization function
2
+ import { initMixpanel, isMixpanelReady } from './mixpanel'; // Import Mixpanel functions
3
+
4
+ import { validateConfig } from './utils/validateConfig';
5
+
6
+ /**
7
+ * Configuration object for initializing the tracking library.
8
+ */
9
+ export interface InitConfig {
10
+ /**
11
+ * The Mixpanel token used for authenticating API requests.
12
+ */
13
+ mixpanelToken: string;
14
+
15
+ /**
16
+ * Optional flag to enable or disable debug mode.
17
+ * When set to true, additional debug information will be logged.
18
+ */
19
+ debug?: boolean;
20
+
21
+ /**
22
+ * Key for tracking user identities.
23
+ */
24
+ identificationKey: string; // Change this line
25
+ }
26
+
27
+ /**
28
+ * Dispatches a 'Tracker Ready' event to the window object.
29
+ *
30
+ * This function creates and dispatches a custom event named 'Tracker Ready'
31
+ * to signal that the tracking library has been successfully initialized and is ready to use.
32
+ */
33
+ function onLoaded() {
34
+ window.dispatchEvent(new CustomEvent('Tracker Ready'));
35
+ }
36
+
37
+ /**
38
+ * Initializes the tracking library with the provided configuration.
39
+ * @param {InitConfig} config - The configuration object for initialization.
40
+ * @throws {Error} Throws an error if the configuration is invalid.
41
+ */
42
+ export async function init(config: InitConfig) {
43
+ validateConfig(config);
44
+
45
+ const { mixpanelToken, debug = false, identificationKey } = config;
46
+
47
+ await initMixpanel(mixpanelToken, debug);
48
+
49
+ // Only mixpanel is required to be ready to dispatch the 'Tracker Ready' event
50
+ try {
51
+ await initFingerprint(identificationKey);
52
+ } catch (error) {
53
+ console.error('Error initializing identification service:', error);
54
+ }
55
+
56
+ // Dispatch the 'Tracker Ready' event
57
+ onLoaded();
58
+ }
59
+
60
+ /**
61
+ * Checks if the tracker is ready.
62
+ *
63
+ * This function verifies whether the Mixpanel tracker has been initialized and is ready to use.
64
+ * @returns {boolean} Returns true if the Mixpanel tracker is ready, otherwise false.
65
+ */
66
+ export function isTrackerReady(): boolean {
67
+ return isMixpanelReady();
68
+ }
@@ -0,0 +1,36 @@
1
+ import mixpanel from 'mixpanel-browser';
2
+
3
+ declare global {
4
+ interface Window {
5
+ mixpanel: typeof mixpanel;
6
+ }
7
+ }
8
+
9
+ /**
10
+ * Initializes Mixpanel with the provided token and debug flag.
11
+ * @param {string} mixpanelToken - The Mixpanel token used for authenticating API requests.
12
+ * @param {boolean} debug - Optional flag to enable or disable debug mode.
13
+ * @returns {Promise<void>} A promise that resolves when Mixpanel is initialized.
14
+ */
15
+ export function initMixpanel(
16
+ mixpanelToken: string,
17
+ debug = false,
18
+ ): Promise<void> {
19
+ return new Promise<void>((resolve) => {
20
+ mixpanel.init(mixpanelToken, {
21
+ debug,
22
+ loaded: () => {
23
+ window.mixpanel = mixpanel;
24
+ resolve();
25
+ },
26
+ });
27
+ });
28
+ }
29
+
30
+ /**
31
+ * Checks if the Mixpanel tracker is ready.
32
+ * @returns {boolean} Returns true if the Mixpanel tracker is ready, otherwise false.
33
+ */
34
+ export function isMixpanelReady(): boolean {
35
+ return window.mixpanel !== undefined;
36
+ }
package/src/track.ts ADDED
@@ -0,0 +1,43 @@
1
+ import mixpanel from 'mixpanel-browser';
2
+ import { getFingerprint } from './fingerprint';
3
+ import { isMixpanelReady } from './mixpanel';
4
+
5
+ /**
6
+ * List of events that trigger the fingerprint to be sent with the event. other eventes will only fetch the cached fingerprint.
7
+ */
8
+ const FP_TRIGGER_EVENTS = ['Track Test'];
9
+
10
+ /**
11
+ * Base function to track events with Mixpanel.
12
+ * This function adds default properties like User Fingerprint.
13
+ * @param {string} eventName - The name of the event to track.
14
+ * @param {object} eventProperties - The properties of the event to track.
15
+ * @throws {Error} Throws an error if Mixpanel or Fingerprint is not ready.
16
+ */
17
+ export async function trackEvent(
18
+ eventName: string,
19
+ eventProperties: object,
20
+ ): Promise<void> {
21
+ if (!isMixpanelReady()) {
22
+ throw new Error('Mixpanel is not initialized.');
23
+ }
24
+
25
+ try {
26
+ const cacheOnly = !FP_TRIGGER_EVENTS.includes(eventName);
27
+
28
+ const fingerprint = await getFingerprint(cacheOnly);
29
+ const defaultProperties = {
30
+ 'User Fingerprint': fingerprint,
31
+ };
32
+
33
+ const properties = {
34
+ ...defaultProperties,
35
+ ...eventProperties,
36
+ };
37
+
38
+ mixpanel.track(eventName, properties);
39
+ } catch (error) {
40
+ console.error('Error tracking event:', error);
41
+ throw error;
42
+ }
43
+ }
@@ -0,0 +1,16 @@
1
+ import { InitConfig } from '../init';
2
+
3
+ /**
4
+ * Validates the initialization configuration for the init function.
5
+ *
6
+ * @param {InitConfig} config - The configuration object to validate.
7
+ * @throws {Error} Throws an error if the configuration is invalid.
8
+ */
9
+ export function validateConfig(config: InitConfig) {
10
+ if (!config.mixpanelToken) {
11
+ throw new Error('mixpanelToken is required');
12
+ }
13
+ if (!config.identificationKey) {
14
+ throw new Error('identificationKey is required'); // Change this line
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />