@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reservamos/browser-analytics",
3
- "version": "1.0.6",
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": "5.26.0"
91
+ "@reservamos/js-api-client": "6.0.0-alpha.2"
92
92
  }
93
93
  }
@@ -18,7 +18,7 @@ function trackCustomEvent(
18
18
  trackEvent(eventName, eventData, meta);
19
19
  } catch (error) {
20
20
  console.error('Error trackCustomEvent:', error);
21
- trackEventError(eventName, error);
21
+ trackEventError(eventName, error, eventData);
22
22
  }
23
23
  }
24
24
 
@@ -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
- fetch(`${apiConfig?.coreUrl}/${apiConfig?.coreVersion}/datalake/identify`, {
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': apiConfig?.coreApiKey || '',
77
- ...(apiConfig?.headers || {}),
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
- try {
103
- if (!mixpanelService.isReady()) {
104
- console.error('Mixpanel is not initialized.');
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
- if (!userId) {
111
- console.error('User ID is required for identification.');
112
- throw new Error('User ID is required for identification.');
113
- }
114
- const anonymousProfile = await createAnonymousProfile(properties);
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
- if (anonymousProfile) {
117
- properties.reservamos_one_id = anonymousProfile.id;
118
- }
119
- const mappedProps = mapProperties(properties);
120
- mixpanelService.identify(userId, mappedProps);
121
- const fingerprint = await fingerprintService.getFingerprint();
122
- if (fingerprint) {
123
- mixpanelService.attachProperty(FINGERPRINT_PROPERTY, fingerprint);
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
- } catch (error) {
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;
@@ -6,7 +6,6 @@ const IdentifySchema = z.object({
6
6
  email: z.string().email().optional(),
7
7
  phone: z
8
8
  .string()
9
- .regex(/^(\+?[1-9]\d{1,14})?$/)
10
9
  .optional(),
11
10
  });
12
11
 
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 { setConfig as setApiConfig } from '@reservamos/js-api-client';
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
- const environment = isSandbox ? 'sandbox' : 'prod';
64
- apiConfig = configService.coreAPIConfig[environment];
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 { core as coreApi } from '@reservamos/js-api-client';
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
- ): AnonymousProfilePayload {
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<AnonymousProfileResponse | undefined> {
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;
@@ -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
- const coreAPIConfig = {
8
+ type CoreAPIConfig = Record<AvailableEnvironments, ApiConfig>;
9
+
10
+ const coreAPIConfig: CoreAPIConfig = {
4
11
  sandbox: {
5
- coreUrl: 'https://datalake-api-dev.reservamossaas.com/api',
6
- coreVersion: 'v1',
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
- coreUrl: 'https://data-lake.reservamossaas.com/api',
14
- coreVersion: 'v1',
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
- coreAPIConfig,
45
+ setEnvironment,
46
+ getCoreAPIConfig,
24
47
  };
25
48
 
26
49
  export default configService;
@@ -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
- return mixpanel.get_distinct_id();
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
- export function trackEventError(eventName: string, error: any): void {
73
- try {
74
- mixpanelService.track('Track Event Error', {
75
- 'Failed Event Name': eventName,
76
- 'Error Message': error?.message ?? 'Failed to track event',
77
- 'Validation Errors': error?.errors || [],
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
 
@@ -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
- }