@reservamos/browser-analytics 1.0.6 → 1.0.9
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.cjs.map +1 -1
- package/dist/browser-analytics.d.ts +4 -6
- package/dist/browser-analytics.esm.js +242 -2606
- package/dist/browser-analytics.esm.js.map +1 -1
- package/dist/browser-analytics.iife.js +3 -3
- package/dist/browser-analytics.iife.js.map +1 -1
- package/package.json +2 -2
- package/src/events/customEvent/trackCustomEvent.ts +1 -1
- package/src/events/identify/identify.ts +29 -32
- package/src/events/identify/identifySchema.ts +0 -1
- package/src/index.ts +0 -1
- package/src/init.ts +4 -22
- package/src/profiles/createAnonymousProfile/createAnonymousProfile.ts +10 -33
- package/src/services/config.ts +33 -10
- package/src/services/mixpanel.ts +22 -7
- package/src/track.ts +27 -10
- package/src/js-api-client.d.ts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reservamos/browser-analytics",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/reservamos/reservamos-browser-analytics.git"
|
|
@@ -88,6 +88,6 @@
|
|
|
88
88
|
"upgradeps": "^2.0.6",
|
|
89
89
|
"vite": "^5.0.12",
|
|
90
90
|
"vitest": "^1.2.1",
|
|
91
|
-
"@reservamos/js-api-client": "
|
|
91
|
+
"@reservamos/js-api-client": "6.0.0-alpha.2"
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { getApiConfig } from '@/init';
|
|
2
1
|
import createAnonymousProfile from '@/profiles/createAnonymousProfile';
|
|
2
|
+
import configService from '@/services/config';
|
|
3
3
|
import geolocationService from '@/services/geolocation';
|
|
4
4
|
import validatorService from '@/services/validator';
|
|
5
|
-
import { trackEventError } from '@/track';
|
|
5
|
+
import { trackEventError, tryTrackEvent } from '@/track';
|
|
6
6
|
import fingerprintService from '../../services/fingerprint';
|
|
7
7
|
import mixpanelService from '../../services/mixpanel';
|
|
8
8
|
import { DefaultProperties } from './identifySchema';
|
|
@@ -53,7 +53,6 @@ const identifyWithLocation = async (
|
|
|
53
53
|
properties: UserProperties,
|
|
54
54
|
) => {
|
|
55
55
|
try {
|
|
56
|
-
const apiConfig = getApiConfig();
|
|
57
56
|
const coordinates = geolocationService.getCoordinates();
|
|
58
57
|
|
|
59
58
|
if (!coordinates) {
|
|
@@ -69,12 +68,13 @@ const identifyWithLocation = async (
|
|
|
69
68
|
delete properties.firstName;
|
|
70
69
|
delete properties.lastName;
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
const coreApiConfig = configService.getCoreAPIConfig();
|
|
72
|
+
fetch(`${coreApiConfig.baseUrl}/v1/datalake/identify`, {
|
|
73
73
|
method: 'POST',
|
|
74
74
|
headers: {
|
|
75
75
|
'Content-Type': 'application/json',
|
|
76
|
-
'Authorization':
|
|
77
|
-
|
|
76
|
+
'Authorization': coreApiConfig.defaultHeaders?.Authorization || '',
|
|
77
|
+
'Origin': coreApiConfig.defaultHeaders?.Origin || '',
|
|
78
78
|
},
|
|
79
79
|
body: JSON.stringify({
|
|
80
80
|
profile_params: {
|
|
@@ -99,35 +99,32 @@ async function identify(
|
|
|
99
99
|
userId: string,
|
|
100
100
|
properties: UserProperties = {},
|
|
101
101
|
): Promise<void> {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
throw new Error('Mixpanel is not initialized.');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
validatorService.parseIdentifyProps(properties);
|
|
102
|
+
tryTrackEvent(async () => {
|
|
103
|
+
try {
|
|
104
|
+
validatorService.parseIdentifyProps(properties);
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
if (!userId) {
|
|
107
|
+
console.error('User ID is required for identification.');
|
|
108
|
+
throw new Error('User ID is required for identification.');
|
|
109
|
+
}
|
|
110
|
+
const anonymousProfile = await createAnonymousProfile(properties);
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
112
|
+
if (anonymousProfile) {
|
|
113
|
+
properties.reservamos_one_id = anonymousProfile.id;
|
|
114
|
+
}
|
|
115
|
+
const mappedProps = mapProperties(properties);
|
|
116
|
+
mixpanelService.identify(userId, mappedProps);
|
|
117
|
+
const fingerprint = await fingerprintService.getFingerprint();
|
|
118
|
+
if (fingerprint) {
|
|
119
|
+
mixpanelService.attachProperty(FINGERPRINT_PROPERTY, fingerprint);
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Error identifying user', error);
|
|
123
|
+
trackEventError('Identify', error, { userId, properties });
|
|
124
|
+
} finally {
|
|
125
|
+
identifyWithLocation(userId, properties);
|
|
124
126
|
}
|
|
125
|
-
}
|
|
126
|
-
console.error('Error identifying user', error);
|
|
127
|
-
trackEventError('Identify', error);
|
|
128
|
-
} finally {
|
|
129
|
-
identifyWithLocation(userId, properties);
|
|
130
|
-
}
|
|
127
|
+
});
|
|
131
128
|
}
|
|
132
129
|
|
|
133
130
|
export default identify;
|
package/src/index.ts
CHANGED
|
@@ -26,7 +26,6 @@ import init, { isTrackerReady } from '@/init';
|
|
|
26
26
|
import createAnonymousProfile from '@/profiles/createAnonymousProfile';
|
|
27
27
|
import fingerprintService from '@/services/fingerprint';
|
|
28
28
|
import mixpanelService from '@/services/mixpanel';
|
|
29
|
-
import './js-api-client.d.ts';
|
|
30
29
|
import trackPurchaseCanceled from './events/purchaseCanceled/trackPurchaseCanceled.js';
|
|
31
30
|
|
|
32
31
|
const analytics = {
|
package/src/init.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import coreApi from '@reservamos/js-api-client/core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import configService from '@/services/config';
|
|
4
4
|
import fingerprintService from '@/services/fingerprint';
|
|
@@ -21,15 +21,6 @@ function onLoaded() {
|
|
|
21
21
|
window.dispatchEvent(new CustomEvent('Tracker Ready'));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
interface ApiConfig {
|
|
25
|
-
coreUrl: string;
|
|
26
|
-
coreVersion: string;
|
|
27
|
-
headers: { Origin: string };
|
|
28
|
-
coreApiKey: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let apiConfig: ApiConfig | null = null;
|
|
32
|
-
|
|
33
24
|
/**
|
|
34
25
|
* Initializes the tracking library with the provided configuration.
|
|
35
26
|
* @param {InitConfig} config - The configuration object for initialization.
|
|
@@ -60,10 +51,8 @@ async function init(config: InitConfig) {
|
|
|
60
51
|
console.error('Error initializing identification service:', error);
|
|
61
52
|
}
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
setApiConfig(apiConfig);
|
|
54
|
+
configService.setEnvironment(isSandbox ? 'sandbox' : 'prod');
|
|
55
|
+
coreApi.setConfig(configService.getCoreAPIConfig());
|
|
67
56
|
|
|
68
57
|
// Dispatch the 'Tracker Ready' event
|
|
69
58
|
onLoaded();
|
|
@@ -79,12 +68,5 @@ function isTrackerReady(): boolean {
|
|
|
79
68
|
return mixpanelService.isReady();
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
* Returns the current API configuration.
|
|
84
|
-
* @returns {ApiConfig | null} The current API configuration or null if not initialized.
|
|
85
|
-
*/
|
|
86
|
-
function getApiConfig(): ApiConfig | null {
|
|
87
|
-
return apiConfig;
|
|
88
|
-
}
|
|
89
|
-
export { isTrackerReady, getApiConfig };
|
|
71
|
+
export { isTrackerReady };
|
|
90
72
|
export default init;
|
|
@@ -1,31 +1,20 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnonymousIdentifier,
|
|
3
|
+
AnonymousProfile,
|
|
4
|
+
CreateAnonymousProfilePayload,
|
|
5
|
+
IdentifierKey,
|
|
6
|
+
} from '@reservamos/js-api-client/core';
|
|
1
7
|
import type { CreateAnonymousProfileProps } from './createAnonymousProfileSchema';
|
|
2
|
-
import
|
|
8
|
+
import coreApi from '@reservamos/js-api-client/core';
|
|
3
9
|
import fingerprintService from '@/services/fingerprint';
|
|
4
10
|
import mixpanelService from '@/services/mixpanel';
|
|
5
11
|
import validatorService from '@/services/validator';
|
|
6
12
|
import CreateAnonymousProfileSchema from './createAnonymousProfileSchema';
|
|
7
13
|
|
|
8
|
-
type IdentifierKey = 'phone' | 'email';
|
|
9
|
-
|
|
10
|
-
interface AnonymousProfilePayload {
|
|
11
|
-
identifier_key: IdentifierKey;
|
|
12
|
-
identifier_value: string;
|
|
13
|
-
identifiers: AnonymousIdentifier[];
|
|
14
|
-
details: Record<string, string>;
|
|
15
|
-
}
|
|
16
|
-
interface AnonymousProfileResponse {
|
|
17
|
-
id: string;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Transforms the identifier object based on the provided values.
|
|
21
|
-
* @param {CreateAnonymousProfileProps} values - The values to transform.
|
|
22
|
-
* @param {AnonymousIdentifier[]} identifiersProps - Additional identifiers to include.
|
|
23
|
-
* @returns {AnonymousProfilePayload} The transformed identifier object.
|
|
24
|
-
*/
|
|
25
14
|
function getAnonymousProfilePayload(
|
|
26
15
|
values: CreateAnonymousProfileProps,
|
|
27
16
|
identifiersProps: AnonymousIdentifier[],
|
|
28
|
-
):
|
|
17
|
+
): CreateAnonymousProfilePayload {
|
|
29
18
|
let identifier_key: IdentifierKey = 'phone';
|
|
30
19
|
let identifier_value: string = values.phone || '';
|
|
31
20
|
const identifiers: AnonymousIdentifier[] = [];
|
|
@@ -65,19 +54,9 @@ function getAnonymousProfilePayload(
|
|
|
65
54
|
};
|
|
66
55
|
}
|
|
67
56
|
|
|
68
|
-
interface AnonymousIdentifier {
|
|
69
|
-
key: string;
|
|
70
|
-
value: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Creates an anonymous profile using the provided payload.
|
|
75
|
-
* @param {CreateAnonymousProfileProps} payload - The payload to create the anonymous profile.
|
|
76
|
-
* @returns {Promise<void>} - A promise that resolves when the anonymous profile is created.
|
|
77
|
-
*/
|
|
78
57
|
async function createAnonymousProfile(
|
|
79
58
|
payload: CreateAnonymousProfileProps,
|
|
80
|
-
): Promise<
|
|
59
|
+
): Promise<AnonymousProfile | undefined> {
|
|
81
60
|
try {
|
|
82
61
|
validatorService.validateProps(payload, CreateAnonymousProfileSchema);
|
|
83
62
|
|
|
@@ -90,9 +69,7 @@ async function createAnonymousProfile(
|
|
|
90
69
|
if (distinctId) identifiers.push({ key: 'distinct_id', value: distinctId });
|
|
91
70
|
|
|
92
71
|
const dataPayload = getAnonymousProfilePayload(payload, identifiers);
|
|
93
|
-
|
|
94
|
-
const result = await coreApi.createAnonymousProfile(dataPayload);
|
|
95
|
-
return result.data;
|
|
72
|
+
return await coreApi.profiles.createAnonymousProfile(dataPayload);
|
|
96
73
|
} catch (error) {
|
|
97
74
|
console.error('Could not create anonymous profile:', error);
|
|
98
75
|
return undefined;
|
package/src/services/config.ts
CHANGED
|
@@ -1,26 +1,49 @@
|
|
|
1
|
+
import type { ApiConfig } from '@reservamos/js-api-client/core';
|
|
2
|
+
|
|
3
|
+
type AvailableEnvironments = 'sandbox' | 'prod';
|
|
4
|
+
|
|
5
|
+
let environment: AvailableEnvironments | undefined;
|
|
1
6
|
const origin = window.location.origin;
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
type CoreAPIConfig = Record<AvailableEnvironments, ApiConfig>;
|
|
9
|
+
|
|
10
|
+
const coreAPIConfig: CoreAPIConfig = {
|
|
4
11
|
sandbox: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
headers: {
|
|
12
|
+
baseUrl: 'https://datalake-api-dev.reservamossaas.com/api',
|
|
13
|
+
defaultHeaders: {
|
|
8
14
|
Origin: origin,
|
|
15
|
+
Authorization: 'Bearer 753bf0710dc920a84236d42241d4f487',
|
|
9
16
|
},
|
|
10
|
-
coreApiKey: 'Bearer 753bf0710dc920a84236d42241d4f487'
|
|
11
17
|
},
|
|
12
18
|
prod: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
headers: {
|
|
19
|
+
baseUrl: 'https://data-lake.reservamossaas.com/api',
|
|
20
|
+
defaultHeaders: {
|
|
16
21
|
Origin: origin,
|
|
22
|
+
Authorization: 'Bearer 753bf0710dc920a84236d42241d4f487',
|
|
17
23
|
},
|
|
18
|
-
coreApiKey:'Bearer 753bf0710dc920a84236d42241d4f487'
|
|
19
24
|
},
|
|
20
25
|
};
|
|
21
26
|
|
|
27
|
+
function setEnvironment(env: AvailableEnvironments) {
|
|
28
|
+
environment = env;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the core API config for the current environment.
|
|
33
|
+
* @throws {Error} Throws an error if the environment is not set.
|
|
34
|
+
*/
|
|
35
|
+
function getCoreAPIConfig(): ApiConfig {
|
|
36
|
+
if (!environment) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'Unable to get core API config, environment not set. Use configService.setEnvironment(environment) to set the environment.',
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return coreAPIConfig[environment];
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
const configService = {
|
|
23
|
-
|
|
45
|
+
setEnvironment,
|
|
46
|
+
getCoreAPIConfig,
|
|
24
47
|
};
|
|
25
48
|
|
|
26
49
|
export default configService;
|
package/src/services/mixpanel.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import mixpanel from 'mixpanel-browser';
|
|
2
|
+
import { version } from '../../package.json';
|
|
2
3
|
|
|
3
4
|
declare global {
|
|
4
5
|
interface Window {
|
|
@@ -6,6 +7,8 @@ declare global {
|
|
|
6
7
|
}
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
const MIXPANEL_DISTINCT_ID_CACHE_KEY = 'mp_distinct_id';
|
|
11
|
+
|
|
9
12
|
/**
|
|
10
13
|
* Initializes Mixpanel with the provided token and debug flag.
|
|
11
14
|
* @param {string} mixpanelToken - The Mixpanel token used for authenticating API requests.
|
|
@@ -38,11 +41,8 @@ function init(
|
|
|
38
41
|
* @throws {Error} If Mixpanel is not initialized (handled internally).
|
|
39
42
|
*/
|
|
40
43
|
function identify(userId: string, properties: Record<string, unknown>): void {
|
|
41
|
-
if (!isReady()) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
44
|
mixpanel.identify(userId);
|
|
45
|
+
updateCachedDistinctId(userId);
|
|
46
46
|
mixpanel.people.set(properties);
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -80,8 +80,7 @@ function track(eventName: string, properties: Record<string, unknown>): void {
|
|
|
80
80
|
if (!isReady()) {
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
mixpanel.track(eventName, properties);
|
|
83
|
+
mixpanel.track(eventName, { ...properties, 'Reservamos Version': version });
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
/**
|
|
@@ -91,11 +90,27 @@ function track(eventName: string, properties: Record<string, unknown>): void {
|
|
|
91
90
|
* @returns {string | null} The distinct ID if available, or null if Mixpanel is not initialized.
|
|
92
91
|
*/
|
|
93
92
|
export const getMixpanelDistinctId = (): string | null => {
|
|
93
|
+
const cachedId = localStorage.getItem(MIXPANEL_DISTINCT_ID_CACHE_KEY);
|
|
94
|
+
|
|
94
95
|
if (mixpanel && mixpanel.get_distinct_id) {
|
|
95
|
-
|
|
96
|
+
const distinctId = mixpanel.get_distinct_id();
|
|
97
|
+
|
|
98
|
+
updateCachedDistinctId(distinctId);
|
|
99
|
+
|
|
100
|
+
return distinctId;
|
|
101
|
+
} else if (cachedId) {
|
|
102
|
+
return cachedId;
|
|
96
103
|
}
|
|
97
104
|
return null;
|
|
98
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Updates the cached Mixpanel distinct ID.
|
|
108
|
+
* Should be called when identifying a user to ensure cache stays in sync.
|
|
109
|
+
* @param {string} distinctId - The distinct ID to cache
|
|
110
|
+
*/
|
|
111
|
+
function updateCachedDistinctId(distinctId: string): void {
|
|
112
|
+
localStorage.setItem(MIXPANEL_DISTINCT_ID_CACHE_KEY, distinctId);
|
|
113
|
+
}
|
|
99
114
|
|
|
100
115
|
const mixpanelService = {
|
|
101
116
|
init,
|
package/src/track.ts
CHANGED
|
@@ -68,16 +68,33 @@ function flattenEventData(data: object): EventData {
|
|
|
68
68
|
* Send an error event to Mixpanel when an event fails to track.
|
|
69
69
|
* The event contains the event name, error message, and validation errors.
|
|
70
70
|
*/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
export function trackEventError(
|
|
72
|
+
eventName: string,
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
+
error: any,
|
|
75
|
+
payload: object,
|
|
76
|
+
): void {
|
|
77
|
+
tryTrackEvent(async () => {
|
|
78
|
+
try {
|
|
79
|
+
mixpanelService.track('Track Event Error', {
|
|
80
|
+
'Failed Event Name': eventName,
|
|
81
|
+
'Error Message': error?.message ?? 'Failed to track event',
|
|
82
|
+
'Validation Errors': error?.errors || [],
|
|
83
|
+
'Event Payload': payload,
|
|
84
|
+
});
|
|
85
|
+
} catch (trackingError) {
|
|
86
|
+
console.error('Failed to track error event:', trackingError);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function tryTrackEvent(trackEventCallback: () => Promise<void>) {
|
|
92
|
+
if (mixpanelService.isReady()) {
|
|
93
|
+
await trackEventCallback();
|
|
94
|
+
} else {
|
|
95
|
+
window.addEventListener('Tracker Ready', async () => {
|
|
96
|
+
await trackEventCallback();
|
|
78
97
|
});
|
|
79
|
-
} catch (trackingError) {
|
|
80
|
-
console.error('Failed to track error event:', trackingError);
|
|
81
98
|
}
|
|
82
99
|
}
|
|
83
100
|
|
|
@@ -126,7 +143,7 @@ export async function trackEvent(
|
|
|
126
143
|
mixpanelService.track(eventName, properties);
|
|
127
144
|
} catch (error) {
|
|
128
145
|
console.error(`Error tracking event '${eventName}':`, error);
|
|
129
|
-
trackEventError(eventName, error);
|
|
146
|
+
trackEventError(eventName, error, eventProperties);
|
|
130
147
|
}
|
|
131
148
|
}
|
|
132
149
|
|
package/src/js-api-client.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
}
|