@reservamos/browser-analytics 0.1.4-alpha.9 → 0.2.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.
- package/dist/browser-analytics.cjs +3 -3
- package/dist/browser-analytics.d.ts +429 -166
- package/dist/browser-analytics.esm.js +2822 -224
- package/dist/browser-analytics.iife.js +3 -3
- package/package.json +2 -1
- package/src/events/customEvent/customEventSchema.ts +19 -0
- package/src/events/customEvent/index.ts +7 -0
- package/src/events/customEvent/trackCustomEvent.ts +19 -0
- package/src/events/identify/identify.ts +17 -10
- package/src/events/identify/identifySchema.ts +2 -1
- package/src/events/identify/index.ts +3 -1
- package/src/events/interestInHome/trackInterestInHome.ts +7 -2
- package/src/events/interestInSearch/trackInterestInSearch.ts +7 -2
- package/src/events/passengersCreated/passengersCreatedSchema.ts +11 -48
- package/src/events/passengersCreated/trackPassengersCreated.ts +12 -3
- package/src/events/paymentAttempt/paymentAttemptSchema.ts +12 -45
- package/src/events/pickedDeparture/pickedDepartureSchema.ts +2 -9
- package/src/events/pickedDeparture/trackPickedDeparture.ts +7 -2
- package/src/events/purchaseAttempt/purchaseAttemptSchema.ts +6 -66
- package/src/events/purchaseAttempt/trackPurchaseAttempt.ts +8 -3
- package/src/events/search/trackSearch.ts +4 -2
- package/src/events/seatChange/trackSeatChange.ts +12 -3
- package/src/events/sharedSchemas/tripSchema.ts +42 -0
- package/src/events/viewResults/trackViewResults.ts +7 -2
- package/src/index.ts +17 -0
- package/src/init.ts +20 -3
- package/src/js-api-client.d.ts +15 -0
- package/src/profiles/createAnonymousProfile/createAnonymousProfile.test.ts +38 -0
- package/src/profiles/createAnonymousProfile/createAnonymousProfile.ts +78 -0
- package/src/profiles/createAnonymousProfile/createAnonymousProfileSchema.ts +15 -0
- package/src/profiles/createAnonymousProfile/index.ts +5 -0
- package/src/services/config.ts +24 -0
- package/src/services/fingerprint.ts +38 -2
- package/src/services/mixpanel.ts +21 -1
- package/src/services/validator.ts +21 -4
- package/src/track.ts +39 -5
- package/src/types/eventData.ts +5 -0
- package/src/util/primitiveFields.ts +70 -0
package/src/init.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { setConfig as setApiConfig } from '@reservamos/js-api-client';
|
|
1
2
|
import { z } from 'zod';
|
|
3
|
+
import configService from '@/services/config';
|
|
2
4
|
import fingerprintService from '@/services/fingerprint';
|
|
3
5
|
import mixpanelService from '@/services/mixpanel';
|
|
4
6
|
import validator, { InitConfigSchema } from './services/validator';
|
|
@@ -26,17 +28,32 @@ function onLoaded() {
|
|
|
26
28
|
export async function init(config: InitConfig) {
|
|
27
29
|
validator.parseInitProps(config);
|
|
28
30
|
|
|
29
|
-
const {
|
|
31
|
+
const {
|
|
32
|
+
mixpanelToken,
|
|
33
|
+
debug = false,
|
|
34
|
+
identificationKey,
|
|
35
|
+
isSandbox = false,
|
|
36
|
+
mixpanelProxyUrl,
|
|
37
|
+
identifyProxyUrl,
|
|
38
|
+
} = config;
|
|
30
39
|
|
|
31
|
-
await mixpanelService.init(mixpanelToken, debug);
|
|
40
|
+
await mixpanelService.init(mixpanelToken, debug, mixpanelProxyUrl);
|
|
32
41
|
|
|
33
42
|
// Only mixpanel is required to be ready to dispatch the 'Tracker Ready' event
|
|
34
43
|
try {
|
|
35
|
-
await fingerprintService.initFingerprint(
|
|
44
|
+
await fingerprintService.initFingerprint(
|
|
45
|
+
identificationKey,
|
|
46
|
+
identifyProxyUrl,
|
|
47
|
+
);
|
|
36
48
|
} catch (error) {
|
|
37
49
|
console.error('Error initializing identification service:', error);
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
const environment = isSandbox ? 'sandbox' : 'prod';
|
|
53
|
+
const apiConfig = configService.coreAPIConfig[environment];
|
|
54
|
+
|
|
55
|
+
setApiConfig(apiConfig);
|
|
56
|
+
|
|
40
57
|
// Dispatch the 'Tracker Ready' event
|
|
41
58
|
onLoaded();
|
|
42
59
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare module '@reservamos/js-api-client' {
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
|
+
export const core: any; // Replace 'any' with a more specific type if possible
|
|
4
|
+
|
|
5
|
+
interface ConfigOptions {
|
|
6
|
+
coreUrl: string;
|
|
7
|
+
coreVersion?: string;
|
|
8
|
+
withCredentials?: boolean;
|
|
9
|
+
headers?: {
|
|
10
|
+
Origin?: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setConfig(options: ConfigOptions): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the createAnonymousProfile method.
|
|
3
|
+
* Cases:
|
|
4
|
+
* - Should not throw an error if the payload is invalid
|
|
5
|
+
* - Should not throw an error if the payload is valid but the API call fails.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CreateAnonymousProfileProps } from './createAnonymousProfileSchema';
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import validatorService from '@/services/validator';
|
|
11
|
+
import createAnonymousProfile from './createAnonymousProfile';
|
|
12
|
+
import CreateAnonymousProfileSchema from './createAnonymousProfileSchema';
|
|
13
|
+
|
|
14
|
+
describe('createAnonymousProfile', () => {
|
|
15
|
+
it('should not throw an error if the payload is invalid', async () => {
|
|
16
|
+
const invalidPayload = {};
|
|
17
|
+
expect(() =>
|
|
18
|
+
validatorService.validateProps(
|
|
19
|
+
invalidPayload,
|
|
20
|
+
CreateAnonymousProfileSchema,
|
|
21
|
+
),
|
|
22
|
+
).toThrow();
|
|
23
|
+
await expect(createAnonymousProfile(invalidPayload)).resolves.not.toThrow();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should not throw an error if the payload is valid but the API call fails', async () => {
|
|
27
|
+
const validPayload: CreateAnonymousProfileProps = {
|
|
28
|
+
email: 'test@test.com',
|
|
29
|
+
};
|
|
30
|
+
expect(() =>
|
|
31
|
+
validatorService.validateProps(
|
|
32
|
+
validPayload,
|
|
33
|
+
CreateAnonymousProfileSchema,
|
|
34
|
+
),
|
|
35
|
+
).not.toThrow();
|
|
36
|
+
await expect(createAnonymousProfile(validPayload)).resolves.not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { CreateAnonymousProfileProps } from './createAnonymousProfileSchema';
|
|
2
|
+
import { core as coreApi } from '@reservamos/js-api-client';
|
|
3
|
+
import fingerprintService from '@/services/fingerprint';
|
|
4
|
+
import mixpanelService from '@/services/mixpanel';
|
|
5
|
+
import validatorService from '@/services/validator';
|
|
6
|
+
import CreateAnonymousProfileSchema from './createAnonymousProfileSchema';
|
|
7
|
+
|
|
8
|
+
type IdentifierKey = 'phone' | 'email';
|
|
9
|
+
|
|
10
|
+
interface AnonymousProfilePayload {
|
|
11
|
+
identifier_key: IdentifierKey;
|
|
12
|
+
identifier_value: string;
|
|
13
|
+
details: Record<string, string | number | boolean>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Transforms the identifier object based on the provided values.
|
|
18
|
+
* @param {CreateAnonymousProfileProps} values - The values to transform.
|
|
19
|
+
* @returns {AnonymousProfilePayload} The transformed identifier object.
|
|
20
|
+
*/
|
|
21
|
+
function getAnonymousProfilePayload(
|
|
22
|
+
values: CreateAnonymousProfileProps,
|
|
23
|
+
): AnonymousProfilePayload {
|
|
24
|
+
let identifier_key: IdentifierKey = 'phone';
|
|
25
|
+
let identifier_value: string = values.phone || '';
|
|
26
|
+
const details: Record<string, string | number | boolean> = {};
|
|
27
|
+
|
|
28
|
+
if (values.email) {
|
|
29
|
+
identifier_key = 'email';
|
|
30
|
+
identifier_value = values.email;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
34
|
+
if (key !== 'email' && key !== identifier_key && value !== undefined) {
|
|
35
|
+
details[key] = value as string | number | boolean;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
identifier_key,
|
|
41
|
+
identifier_value,
|
|
42
|
+
details,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface AnonymousIdentifier {
|
|
47
|
+
key: string;
|
|
48
|
+
value: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates an anonymous profile using the provided payload.
|
|
53
|
+
* @param {CreateAnonymousProfileProps} payload - The payload to create the anonymous profile.
|
|
54
|
+
* @returns {Promise<void>} - A promise that resolves when the anonymous profile is created.
|
|
55
|
+
*/
|
|
56
|
+
async function createAnonymousProfile(
|
|
57
|
+
payload: CreateAnonymousProfileProps,
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
try {
|
|
60
|
+
validatorService.validateProps(payload, CreateAnonymousProfileSchema);
|
|
61
|
+
|
|
62
|
+
const identifiers: AnonymousIdentifier[] = [];
|
|
63
|
+
const userFingerprintId = fingerprintService.getCachedFingerprint();
|
|
64
|
+
const distinctId = mixpanelService.getMixpanelDistinctId();
|
|
65
|
+
|
|
66
|
+
if (userFingerprintId)
|
|
67
|
+
identifiers.push({ key: 'fingerprint_id', value: userFingerprintId });
|
|
68
|
+
if (distinctId) identifiers.push({ key: 'distinct_id', value: distinctId });
|
|
69
|
+
|
|
70
|
+
const dataPayload = getAnonymousProfilePayload(payload);
|
|
71
|
+
|
|
72
|
+
await coreApi.createAnonymousProfile({ ...dataPayload, identifiers });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Could not create anonymous profile:', error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default createAnonymousProfile;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const CreateAnonymousProfileSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
email: z.string().email().optional(),
|
|
6
|
+
phone: z.string().optional(),
|
|
7
|
+
})
|
|
8
|
+
.refine((data) => data.email || data.phone, {
|
|
9
|
+
message: "At least one of 'email' or 'phone' must be provided",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type CreateAnonymousProfileProps = z.infer<typeof CreateAnonymousProfileSchema>;
|
|
13
|
+
|
|
14
|
+
export type { CreateAnonymousProfileProps };
|
|
15
|
+
export default CreateAnonymousProfileSchema;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const origin = window.location.origin;
|
|
2
|
+
|
|
3
|
+
const coreAPIConfig = {
|
|
4
|
+
sandbox: {
|
|
5
|
+
coreUrl: 'https://datalake-api-dev.reservamossaas.com/api',
|
|
6
|
+
coreVersion: 'v1',
|
|
7
|
+
headers: {
|
|
8
|
+
Origin: origin,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
prod: {
|
|
12
|
+
coreUrl: 'https://datalake-api-dev.reservamossaas.com/api',
|
|
13
|
+
coreVersion: 'v1',
|
|
14
|
+
headers: {
|
|
15
|
+
Origin: origin,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const configService = {
|
|
21
|
+
coreAPIConfig,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default configService;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
FingerprintJSPro,
|
|
3
|
+
FpjsClient,
|
|
4
|
+
} from '@fingerprintjs/fingerprintjs-pro-spa';
|
|
2
5
|
|
|
3
6
|
declare global {
|
|
4
7
|
interface Window {
|
|
@@ -6,12 +9,15 @@ declare global {
|
|
|
6
9
|
fingerprintConfig: {
|
|
7
10
|
cacheName?: string;
|
|
8
11
|
cacheTimeInDays?: number;
|
|
12
|
+
proxyUrl?: string;
|
|
9
13
|
};
|
|
10
14
|
}
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
const DEFAULT_CACHE_NAME = 'default_fingerprint_cache';
|
|
14
18
|
const DEFAULT_CACHE_TIME_IN_DAYS = 7;
|
|
19
|
+
const PROXY_QUERY_PARAM =
|
|
20
|
+
'apiKey=<apiKey>&version=<version>&loaderVersion=<loaderVersion>';
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* Calculates the expiration date based on the cache time.
|
|
@@ -62,17 +68,46 @@ const setCachedFingerprint = (fingerprint: string): void => {
|
|
|
62
68
|
localStorage.setItem(cacheName, JSON.stringify(cacheData));
|
|
63
69
|
};
|
|
64
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Builds a proxy URL by appending a query parameter to the given URL.
|
|
73
|
+
*
|
|
74
|
+
* This function takes a base URL and appends a predefined query parameter
|
|
75
|
+
* to create a proxy URL. The query parameter is defined by the constant
|
|
76
|
+
* PROXY_QUERY_PARAM, which should be declared elsewhere in the file.
|
|
77
|
+
* @param {string} url - The base URL to which the proxy query parameter will be appended.
|
|
78
|
+
* @returns {string} The constructed proxy URL with the appended query parameter.
|
|
79
|
+
*/
|
|
80
|
+
function buildProxyUrl(url: string) {
|
|
81
|
+
return `${url}?${PROXY_QUERY_PARAM}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
/**
|
|
66
85
|
* Initializes identification service with the provided API key and stores the instance in the window object.
|
|
67
86
|
* @param {string} apiKey - The API key for Fingerprint.js.
|
|
87
|
+
* @param {string} proxyUrl - Optional proxy URL for Fingerprint.js requests.
|
|
68
88
|
* @returns {Promise<void>} A promise that resolves when initialization is complete.
|
|
69
89
|
* @throws {Error} Throws an error if initialization fails.
|
|
70
90
|
*/
|
|
71
|
-
async function initFingerprint(
|
|
91
|
+
async function initFingerprint(
|
|
92
|
+
apiKey: string,
|
|
93
|
+
proxyUrl?: string,
|
|
94
|
+
): Promise<void> {
|
|
95
|
+
const customEndpointArray = [FingerprintJSPro.defaultEndpoint];
|
|
96
|
+
|
|
97
|
+
const customScriptEndpointArray = [FingerprintJSPro.defaultScriptUrlPattern];
|
|
98
|
+
|
|
99
|
+
if (proxyUrl) {
|
|
100
|
+
// @ts-expect-error - This is a valid operation but fingerprintjs-pro-spa types are incorrect
|
|
101
|
+
customEndpointArray.unshift(proxyUrl);
|
|
102
|
+
customScriptEndpointArray.unshift(buildProxyUrl(proxyUrl));
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
window.fingerprintConfig = {};
|
|
73
106
|
window.fpClient = new FpjsClient({
|
|
74
107
|
loadOptions: {
|
|
75
108
|
apiKey,
|
|
109
|
+
endpoint: customEndpointArray,
|
|
110
|
+
scriptUrlPattern: customScriptEndpointArray,
|
|
76
111
|
},
|
|
77
112
|
});
|
|
78
113
|
|
|
@@ -134,6 +169,7 @@ const fingerprintService = {
|
|
|
134
169
|
initFingerprint,
|
|
135
170
|
getFingerprint,
|
|
136
171
|
isFingerprintReady,
|
|
172
|
+
getCachedFingerprint,
|
|
137
173
|
};
|
|
138
174
|
|
|
139
175
|
export default fingerprintService;
|
package/src/services/mixpanel.ts
CHANGED
|
@@ -10,9 +10,14 @@ declare global {
|
|
|
10
10
|
* Initializes Mixpanel with the provided token and debug flag.
|
|
11
11
|
* @param {string} mixpanelToken - The Mixpanel token used for authenticating API requests.
|
|
12
12
|
* @param {boolean} debug - Optional flag to enable or disable debug mode.
|
|
13
|
+
* @param {string} proxyUrl - Optional proxy URL for Mixpanel requests.
|
|
13
14
|
* @returns {Promise<void>} A promise that resolves when Mixpanel is initialized.
|
|
14
15
|
*/
|
|
15
|
-
function init(
|
|
16
|
+
function init(
|
|
17
|
+
mixpanelToken: string,
|
|
18
|
+
debug = false,
|
|
19
|
+
proxyUrl?: string,
|
|
20
|
+
): Promise<void> {
|
|
16
21
|
return new Promise<void>((resolve) => {
|
|
17
22
|
mixpanel.init(mixpanelToken, {
|
|
18
23
|
debug,
|
|
@@ -20,6 +25,7 @@ function init(mixpanelToken: string, debug = false): Promise<void> {
|
|
|
20
25
|
window.mixpanel = mixpanel;
|
|
21
26
|
resolve();
|
|
22
27
|
},
|
|
28
|
+
...(proxyUrl && { api_host: proxyUrl }),
|
|
23
29
|
});
|
|
24
30
|
});
|
|
25
31
|
}
|
|
@@ -78,12 +84,26 @@ function track(eventName: string, properties: Record<string, unknown>): void {
|
|
|
78
84
|
mixpanel.track(eventName, properties);
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Retrieves the distinct ID from Mixpanel.
|
|
89
|
+
* This function checks if Mixpanel is initialized and returns the current user's
|
|
90
|
+
* distinct ID. If Mixpanel is not initialized, it returns null.
|
|
91
|
+
* @returns {string | null} The distinct ID if available, or null if Mixpanel is not initialized.
|
|
92
|
+
*/
|
|
93
|
+
export const getMixpanelDistinctId = (): string | null => {
|
|
94
|
+
if (mixpanel && mixpanel.get_distinct_id) {
|
|
95
|
+
return mixpanel.get_distinct_id();
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
99
|
+
|
|
81
100
|
const mixpanelService = {
|
|
82
101
|
init,
|
|
83
102
|
isReady,
|
|
84
103
|
track,
|
|
85
104
|
identify,
|
|
86
105
|
attachProperty,
|
|
106
|
+
getMixpanelDistinctId,
|
|
87
107
|
};
|
|
88
108
|
|
|
89
109
|
export default mixpanelService;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z, ZodError, ZodSchema } from 'zod';
|
|
2
|
+
import { customEventSchema } from '@/events/customEvent/customEventSchema';
|
|
2
3
|
import IdentifySchema from '@/events/identify/identifySchema';
|
|
3
4
|
import { interestInHomeSchema } from '@/events/interestInHome';
|
|
4
5
|
import { interestInSearchSchema } from '@/events/interestInSearch';
|
|
@@ -25,6 +26,19 @@ export const InitConfigSchema = z.object({
|
|
|
25
26
|
* Key for tracking user identities.
|
|
26
27
|
*/
|
|
27
28
|
identificationKey: z.string().min(1, 'Identification key is required'),
|
|
29
|
+
/**
|
|
30
|
+
* Optional flag to determine if the sandbox environment should be used.
|
|
31
|
+
* When set to true, the sandbox environment will be used; otherwise, the production environment will be used.
|
|
32
|
+
*/
|
|
33
|
+
isSandbox: z.boolean().optional().default(false),
|
|
34
|
+
/**
|
|
35
|
+
* Optional proxy URL for Mixpanel requests.
|
|
36
|
+
*/
|
|
37
|
+
mixpanelProxyUrl: z.string().optional(),
|
|
38
|
+
/**
|
|
39
|
+
* Optional proxy URL for identify requests.
|
|
40
|
+
*/
|
|
41
|
+
identifyProxyUrl: z.string().optional(),
|
|
28
42
|
});
|
|
29
43
|
interface CustomError {
|
|
30
44
|
field: string;
|
|
@@ -82,16 +96,18 @@ const eventSchemas: Record<string, z.ZodSchema> = {
|
|
|
82
96
|
'Picked Departure': pickedDepartureSchema,
|
|
83
97
|
};
|
|
84
98
|
|
|
85
|
-
type
|
|
99
|
+
type EventDataSchema = z.infer<
|
|
100
|
+
(typeof eventSchemas)[keyof typeof eventSchemas]
|
|
101
|
+
>;
|
|
86
102
|
|
|
87
103
|
/**
|
|
88
104
|
* Validates the event data against the predefined schema for the given event name.
|
|
89
105
|
* @param {string} eventName - The name of the event to validate.
|
|
90
|
-
* @param {
|
|
106
|
+
* @param {EventDataSchema} eventData - The data of the event to validate.
|
|
91
107
|
* @throws {Error} - Throws an error if validation fails.
|
|
92
108
|
*/
|
|
93
|
-
function parseEventProps(eventName: string, eventData:
|
|
94
|
-
const eventSchema = eventSchemas[eventName];
|
|
109
|
+
function parseEventProps(eventName: string, eventData: EventDataSchema): void {
|
|
110
|
+
const eventSchema = eventSchemas[eventName] || customEventSchema;
|
|
95
111
|
|
|
96
112
|
if (!eventSchema) {
|
|
97
113
|
throw { message: `Event ${eventName} not found` };
|
|
@@ -148,6 +164,7 @@ const validatorService = {
|
|
|
148
164
|
parseEventProps,
|
|
149
165
|
parseInitProps,
|
|
150
166
|
parseIdentifyProps,
|
|
167
|
+
validateProps,
|
|
151
168
|
};
|
|
152
169
|
|
|
153
170
|
// Export the validator object
|
package/src/track.ts
CHANGED
|
@@ -1,22 +1,55 @@
|
|
|
1
1
|
import fingerprintService from '@/services/fingerprint';
|
|
2
2
|
import mixpanelService from '@/services/mixpanel';
|
|
3
3
|
import validator from '@/services/validator';
|
|
4
|
+
import EventData, { AllowedPrimitive } from './types/eventData';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* List of events that trigger the fingerprint to be sent with the event. other
|
|
7
|
+
* List of events that trigger the fingerprint to be sent with the event. other events will only fetch the cached fingerprint.
|
|
7
8
|
*/
|
|
8
|
-
const FP_TRIGGER_EVENTS = ['
|
|
9
|
+
const FP_TRIGGER_EVENTS = ['Search', 'View Results'];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Simplifies the structure of event data by flattening nested objects and arrays.
|
|
13
|
+
* @param {object} data - The event data to simplify.
|
|
14
|
+
* @returns {EventData} - The flattened event data.
|
|
15
|
+
*/
|
|
16
|
+
function flattenEventData(data: object): EventData {
|
|
17
|
+
return Object.entries(data).reduce((flattenedData, [key, value]) => {
|
|
18
|
+
if (!Array.isArray(value)) {
|
|
19
|
+
flattenedData[key] = value as AllowedPrimitive;
|
|
20
|
+
return flattenedData;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
value.forEach((item) => {
|
|
24
|
+
if (typeof item !== 'object' || item === null) return;
|
|
25
|
+
|
|
26
|
+
Object.entries(item).forEach(([innerKey, innerValue]) => {
|
|
27
|
+
if (!flattenedData[innerKey]) {
|
|
28
|
+
flattenedData[innerKey] = [innerValue as AllowedPrimitive];
|
|
29
|
+
} else {
|
|
30
|
+
(flattenedData[innerKey] as AllowedPrimitive[]).push(
|
|
31
|
+
innerValue as AllowedPrimitive,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return flattenedData;
|
|
38
|
+
}, {} as EventData);
|
|
39
|
+
}
|
|
9
40
|
|
|
10
41
|
/**
|
|
11
42
|
* Base function to track events with Mixpanel.
|
|
12
43
|
* This function adds default properties like User Fingerprint.
|
|
13
44
|
* @param {string} eventName - The name of the event to track.
|
|
14
45
|
* @param {object} eventProperties - The properties of the event to track.
|
|
46
|
+
* @param {EventData} meta - Additional metadata to include in the event.
|
|
15
47
|
* @throws {Error} Throws an error if Mixpanel or Fingerprint is not ready.
|
|
16
48
|
*/
|
|
17
49
|
export async function trackEvent(
|
|
18
50
|
eventName: string,
|
|
19
51
|
eventProperties: object,
|
|
52
|
+
meta: EventData = {},
|
|
20
53
|
): Promise<void> {
|
|
21
54
|
if (!mixpanelService.isReady()) {
|
|
22
55
|
throw new Error('Mixpanel is not initialized.');
|
|
@@ -31,14 +64,15 @@ export async function trackEvent(
|
|
|
31
64
|
'User Fingerprint': fingerprint,
|
|
32
65
|
};
|
|
33
66
|
|
|
67
|
+
const simplifiedData = flattenEventData(eventProperties);
|
|
34
68
|
const properties = {
|
|
35
69
|
...defaultProperties,
|
|
36
|
-
...
|
|
70
|
+
...simplifiedData,
|
|
71
|
+
...meta,
|
|
37
72
|
};
|
|
38
73
|
|
|
39
74
|
mixpanelService.track(eventName, properties);
|
|
40
75
|
} catch (error) {
|
|
41
|
-
console.error(
|
|
42
|
-
throw error;
|
|
76
|
+
console.error(`Error tracking event '${eventName}':`, error);
|
|
43
77
|
}
|
|
44
78
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import dateValidation from '@/util/dateValidation';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Zod schema for a string field with a custom error message.
|
|
6
|
+
* @param {string} fieldName - The name of the field to be validated.
|
|
7
|
+
* @returns {z.ZodEffects<z.ZodString, string, string>} A Zod schema for string validation.
|
|
8
|
+
*/
|
|
9
|
+
export const stringField = (fieldName: string) =>
|
|
10
|
+
z.string().refine((val) => val, {
|
|
11
|
+
message: `${fieldName} must be a string`,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a Zod schema for a number field with a custom error message.
|
|
16
|
+
* @param {string} fieldName - The name of the field to be validated.
|
|
17
|
+
* @returns {z.ZodEffects<z.ZodNumber, number, number>} A Zod schema for number validation.
|
|
18
|
+
*/
|
|
19
|
+
export const numberField = (fieldName: string) =>
|
|
20
|
+
z.number().refine((val) => val, {
|
|
21
|
+
message: `${fieldName} must be a number`,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Zod schema for an integer field with a custom error message.
|
|
26
|
+
* @param {string} fieldName - The name of the field to be validated.
|
|
27
|
+
* @returns {z.ZodEffects<z.ZodNumber, number, number>} A Zod schema for integer validation.
|
|
28
|
+
*/
|
|
29
|
+
export const intField = (fieldName: string) =>
|
|
30
|
+
z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.refine((val) => val, {
|
|
34
|
+
message: `${fieldName} must be an integer`,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a Zod schema for a date field with custom validation and error message.
|
|
39
|
+
* @param {string} fieldName - The name of the field to be validated.
|
|
40
|
+
* @returns {z.ZodEffects<z.ZodString, string, string>} A Zod schema for date validation.
|
|
41
|
+
*/
|
|
42
|
+
export const dateField = (fieldName: string) =>
|
|
43
|
+
z.string().refine(dateValidation, {
|
|
44
|
+
message: `Invalid ${fieldName} datetime format`,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a Zod schema for an array field with a custom schema and optional minimum length.
|
|
49
|
+
* @param {z.ZodType<T>} schema - The Zod schema to be applied to each array element.
|
|
50
|
+
* @param {string} fieldName - The name of the field to be validated.
|
|
51
|
+
* @param {number} [minLength] - The optional minimum length of the array.
|
|
52
|
+
* @returns {z.ZodArray<z.ZodType<T>>} A Zod schema for array validation.
|
|
53
|
+
* @template T
|
|
54
|
+
*/
|
|
55
|
+
export const arrayField = <T>(
|
|
56
|
+
schema: z.ZodType<T>,
|
|
57
|
+
fieldName: string,
|
|
58
|
+
minLength?: number,
|
|
59
|
+
) => {
|
|
60
|
+
const arraySchema = z.array(schema);
|
|
61
|
+
|
|
62
|
+
if (minLength !== undefined) {
|
|
63
|
+
return arraySchema.min(
|
|
64
|
+
minLength,
|
|
65
|
+
`${fieldName} must have at least ${minLength} items`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return arraySchema;
|
|
70
|
+
};
|