@reservamos/browser-analytics 1.0.9 → 1.0.10
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 +134 -1
- package/dist/browser-analytics.esm.js +790 -361
- 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/index.ts +17 -1
- package/src/recommendations/createRecommendedSeats/createRecommendedSeats.ts +40 -0
- package/src/recommendations/createRecommendedSeats/createRecommendedSeatsSchema.ts +25 -0
- package/src/recommendations/createRecommendedSeats/index.ts +5 -0
- package/src/recommendations/getRecommendedPlaces/getRecommendedPlaces.ts +63 -0
- package/src/recommendations/getRecommendedPlaces/index.ts +3 -0
- package/src/recommendations/getRecommendedSeats/getRecommendedSeats.ts +37 -0
- package/src/recommendations/getRecommendedSeats/getRecommendedSeatsSchema.ts +24 -0
- package/src/recommendations/getRecommendedSeats/index.ts +5 -0
- package/src/recommendations/getRecommendedTrips/getRecommendedTrips.ts +36 -0
- package/src/recommendations/getRecommendedTrips/getRecommendedTripsSchema.ts +10 -0
- package/src/recommendations/getRecommendedTrips/index.ts +5 -0
- package/src/services/geolocation.ts +34 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reservamos/browser-analytics",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
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": "6.0.0-alpha.
|
|
91
|
+
"@reservamos/js-api-client": "6.0.0-alpha.5"
|
|
92
92
|
}
|
|
93
93
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,10 @@ import type { SearchProps } from '@/events/search';
|
|
|
10
10
|
import type { SeatChangeProps } from '@/events/seatChange';
|
|
11
11
|
import type { ViewResultsProps } from '@/events/viewResults';
|
|
12
12
|
import type { CreateAnonymousProfileProps } from '@/profiles/createAnonymousProfile';
|
|
13
|
+
import type { CreateRecommendedSeatsSchemaProps } from '@/recommendations/createRecommendedSeats';
|
|
14
|
+
import type { GetRecommendedSeatsSchemaProps } from '@/recommendations/getRecommendedSeats';
|
|
13
15
|
import type { EventData, EventMetadata } from '@/types/eventData';
|
|
16
|
+
import type { GetRecommendedTripsProps } from '@/recommendations/getRecommendedTrips';
|
|
14
17
|
import trackCustomEvent from '@/events/customEvent';
|
|
15
18
|
import identify from '@/events/identify';
|
|
16
19
|
import trackInterestInHome from '@/events/interestInHome';
|
|
@@ -19,14 +22,18 @@ import trackPassengersCreated from '@/events/passengersCreated';
|
|
|
19
22
|
import trackPaymentAttempt from '@/events/paymentAttempt';
|
|
20
23
|
import trackPickedDeparture from '@/events/pickedDeparture';
|
|
21
24
|
import trackPurchaseAttempt from '@/events/purchaseAttempt';
|
|
25
|
+
import trackPurchaseCanceled from '@/events/purchaseCanceled';
|
|
22
26
|
import trackSearch from '@/events/search';
|
|
23
27
|
import trackSeatChange from '@/events/seatChange';
|
|
24
28
|
import trackViewResults from '@/events/viewResults';
|
|
25
29
|
import init, { isTrackerReady } from '@/init';
|
|
26
30
|
import createAnonymousProfile from '@/profiles/createAnonymousProfile';
|
|
31
|
+
import createRecommendedSeats from '@/recommendations/createRecommendedSeats';
|
|
32
|
+
import getRecommendedPlaces from '@/recommendations/getRecommendedPlaces';
|
|
33
|
+
import getRecommendedSeats from '@/recommendations/getRecommendedSeats';
|
|
34
|
+
import getRecommendedTrips from '@/recommendations/getRecommendedTrips';
|
|
27
35
|
import fingerprintService from '@/services/fingerprint';
|
|
28
36
|
import mixpanelService from '@/services/mixpanel';
|
|
29
|
-
import trackPurchaseCanceled from './events/purchaseCanceled/trackPurchaseCanceled.js';
|
|
30
37
|
|
|
31
38
|
const analytics = {
|
|
32
39
|
init,
|
|
@@ -39,6 +46,12 @@ const analytics = {
|
|
|
39
46
|
profiles: {
|
|
40
47
|
createAnonymousProfile,
|
|
41
48
|
},
|
|
49
|
+
recommendations: {
|
|
50
|
+
getRecommendedPlaces,
|
|
51
|
+
createRecommendedSeats,
|
|
52
|
+
getRecommendedSeats,
|
|
53
|
+
getRecommendedTrips,
|
|
54
|
+
},
|
|
42
55
|
track: {
|
|
43
56
|
search: trackSearch,
|
|
44
57
|
seatChange: trackSeatChange,
|
|
@@ -69,6 +82,9 @@ export type {
|
|
|
69
82
|
EventMetadata,
|
|
70
83
|
EventData,
|
|
71
84
|
PurchaseCanceledProps,
|
|
85
|
+
GetRecommendedSeatsSchemaProps,
|
|
86
|
+
CreateRecommendedSeatsSchemaProps,
|
|
87
|
+
GetRecommendedTripsProps,
|
|
72
88
|
};
|
|
73
89
|
|
|
74
90
|
export default analytics;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RecommendedSeatsPayload,
|
|
3
|
+
RecommendedSeatsResponse,
|
|
4
|
+
} from '@reservamos/js-api-client/core';
|
|
5
|
+
import coreApi from '@reservamos/js-api-client/core';
|
|
6
|
+
import fingerprintService from '@/services/fingerprint';
|
|
7
|
+
import mixpanelService from '@/services/mixpanel';
|
|
8
|
+
import validatorService from '@/services/validator';
|
|
9
|
+
import CreateRecommendedSeatsSchema from './createRecommendedSeatsSchema';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates recommended seats by combining the provided payload with user identification data
|
|
13
|
+
* from Mixpanel and Fingerprint services.
|
|
14
|
+
* @throws {Error} When no distinct ID is found
|
|
15
|
+
* @throws {Error} When payload validation fails
|
|
16
|
+
* @throws {Error} When the API request fails
|
|
17
|
+
*/
|
|
18
|
+
async function createRecommendedSeats(
|
|
19
|
+
payload: RecommendedSeatsPayload,
|
|
20
|
+
): Promise<RecommendedSeatsResponse> {
|
|
21
|
+
try {
|
|
22
|
+
validatorService.validateProps(payload, CreateRecommendedSeatsSchema);
|
|
23
|
+
const distinctId = mixpanelService.getMixpanelDistinctId();
|
|
24
|
+
const userFingerprintId = fingerprintService.getCachedFingerprint();
|
|
25
|
+
if (!distinctId) {
|
|
26
|
+
throw new Error('No distinct ID found');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return await coreApi.recommendations.createRecommendedSeats({
|
|
30
|
+
...payload,
|
|
31
|
+
distinct_id: distinctId,
|
|
32
|
+
device_fingerprint: userFingerprintId || '',
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Could not create recommended seats:', error);
|
|
36
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default createRecommendedSeats;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const SeatSchema = z.object({
|
|
4
|
+
category: z.string(),
|
|
5
|
+
number: z.string().optional(),
|
|
6
|
+
occupied: z.boolean().optional(),
|
|
7
|
+
adjacent_seats: z.null().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const BusSchemeSchema = z.object({
|
|
11
|
+
bus: z.array(z.array(z.array(SeatSchema))),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const CreateRecommendedSeatsSchema = z.object({
|
|
15
|
+
bus_type: z.string(),
|
|
16
|
+
selected_seats: z.string(),
|
|
17
|
+
bus_scheme: BusSchemeSchema,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
type CreateRecommendedSeatsSchemaProps = z.infer<
|
|
21
|
+
typeof CreateRecommendedSeatsSchema
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
export type { CreateRecommendedSeatsSchemaProps };
|
|
25
|
+
export default CreateRecommendedSeatsSchema;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { RecommendedPlacesResponse } from '@reservamos/js-api-client/core';
|
|
2
|
+
import coreApi from '@reservamos/js-api-client/core';
|
|
3
|
+
import mixpanelService from '@/services/mixpanel';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Polls the recommended places API until a final state is reached
|
|
7
|
+
* @throws Will reject the promise if the polling status returns 'failed'
|
|
8
|
+
* @private
|
|
9
|
+
*/
|
|
10
|
+
const pollingRecommendedPlaces = async (
|
|
11
|
+
response: RecommendedPlacesResponse,
|
|
12
|
+
) => {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const { state, polling_id } = response;
|
|
15
|
+
if (state === 'finished') {
|
|
16
|
+
resolve(response);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
coreApi.recommendations.pollRecommendedPlaces(polling_id, {
|
|
20
|
+
start: true,
|
|
21
|
+
watch: 'state',
|
|
22
|
+
expect: 'finished',
|
|
23
|
+
onEachResponse: (profile) => {
|
|
24
|
+
if (profile.status === 'finished') {
|
|
25
|
+
resolve(profile);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (profile.status === 'failed') {
|
|
29
|
+
reject(profile);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fetches personalized place recommendations for a user
|
|
39
|
+
* This function retrieves recommended places based on the user's Mixpanel distinct ID.
|
|
40
|
+
* It initiates the recommendation process and polls until the results are ready.
|
|
41
|
+
* @returns {Promise<RecommendedPlacesResponse>} A promise that resolves with the recommended places
|
|
42
|
+
* @throws {Error} When no Mixpanel distinct ID is found
|
|
43
|
+
* @throws {Error} When no response is received from the recommendations API
|
|
44
|
+
*/
|
|
45
|
+
async function getRecommendedPlaces(): Promise<RecommendedPlacesResponse> {
|
|
46
|
+
const distinctId = mixpanelService.getMixpanelDistinctId();
|
|
47
|
+
if (!distinctId) {
|
|
48
|
+
throw new Error('No distinct ID found');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const response = await coreApi.recommendations.createRecommendedPlaces({
|
|
52
|
+
distinct_id: distinctId,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!response) {
|
|
56
|
+
throw new Error('No response received');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const profile = await pollingRecommendedPlaces(response);
|
|
60
|
+
return profile as RecommendedPlacesResponse;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default getRecommendedPlaces;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GetRecommendedSeatsPayload,
|
|
3
|
+
GetRecommendedSeatsResponse,
|
|
4
|
+
} from '@reservamos/js-api-client/core';
|
|
5
|
+
import coreApi from '@reservamos/js-api-client/core';
|
|
6
|
+
import mixpanelService from '@/services/mixpanel';
|
|
7
|
+
import validatorService from '@/services/validator';
|
|
8
|
+
import GetRecommendedSeatsSchema from './getRecommendedSeatsSchema';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create recommended seats by combining the provided payload with data from
|
|
12
|
+
* user identification from Mixpanel and Fingerprint services.
|
|
13
|
+
* @throws {Error} When the payload fails schema validation
|
|
14
|
+
* @throws {Error} When no Mixpanel distinct ID is found
|
|
15
|
+
* @throws {Error} When the API request fails
|
|
16
|
+
*/
|
|
17
|
+
async function createRecommendedSeats(
|
|
18
|
+
payload: GetRecommendedSeatsPayload,
|
|
19
|
+
): Promise<GetRecommendedSeatsResponse> {
|
|
20
|
+
try {
|
|
21
|
+
validatorService.validateProps(payload, GetRecommendedSeatsSchema);
|
|
22
|
+
const distinctId = mixpanelService.getMixpanelDistinctId();
|
|
23
|
+
if (!distinctId) {
|
|
24
|
+
throw new Error('No distinct ID found');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return await coreApi.recommendations.getRecommendedSeats({
|
|
28
|
+
...payload,
|
|
29
|
+
distinct_id: distinctId,
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Could not create recommended seats:', error);
|
|
33
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default createRecommendedSeats;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const SeatSchema = z.object({
|
|
4
|
+
category: z.string(),
|
|
5
|
+
number: z.string().optional(),
|
|
6
|
+
occupied: z.boolean().optional(),
|
|
7
|
+
adjacent_seats: z.null().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const BusSchemeSchema = z.object({
|
|
11
|
+
bus: z.array(z.array(z.array(SeatSchema))),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const GetRecommendedSeatsSchema = z.object({
|
|
15
|
+
bus_scheme: BusSchemeSchema,
|
|
16
|
+
total_seats: z.number()
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
type GetRecommendedSeatsSchemaProps = z.infer<
|
|
20
|
+
typeof GetRecommendedSeatsSchema
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export type { GetRecommendedSeatsSchemaProps };
|
|
24
|
+
export default GetRecommendedSeatsSchema;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RecommendedTripsResponse,
|
|
3
|
+
GetRecommendedTripsPayload
|
|
4
|
+
} from '@reservamos/js-api-client/core';
|
|
5
|
+
import coreApi from '@reservamos/js-api-client/core';
|
|
6
|
+
import fingerprintService from '@/services/fingerprint';
|
|
7
|
+
import mixpanelService from '@/services/mixpanel';
|
|
8
|
+
import validatorService from '@/services/validator';
|
|
9
|
+
import GetRecommendedTripsSchema from './getRecommendedTripsSchema';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fetches recommended trips based on the provided search ID.
|
|
13
|
+
* @throws Error if user identifier is not loaded correctly or if the api request fails
|
|
14
|
+
*/
|
|
15
|
+
async function getRecommendedTrips({
|
|
16
|
+
searchId,
|
|
17
|
+
}: GetRecommendedTripsPayload): Promise<RecommendedTripsResponse> {
|
|
18
|
+
try {
|
|
19
|
+
validatorService.validateProps({ searchId }, GetRecommendedTripsSchema);
|
|
20
|
+
const identifier =
|
|
21
|
+
fingerprintService.getCachedFingerprint() ||
|
|
22
|
+
mixpanelService.getMixpanelDistinctId();
|
|
23
|
+
if (!identifier) throw new Error('No identifier id');
|
|
24
|
+
|
|
25
|
+
const response = await coreApi.recommendations.getRecommendedTrips({
|
|
26
|
+
searchId,
|
|
27
|
+
userIdentifier: String(identifier),
|
|
28
|
+
});
|
|
29
|
+
return response;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Could not get recommended trips:', error);
|
|
32
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default getRecommendedTrips;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const GetRecommendedTripsSchema = z.object({
|
|
4
|
+
searchId: z.number().min(1, 'SearchId is required'),
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
type GetRecommendedTripsProps = z.infer<typeof GetRecommendedTripsSchema>;
|
|
8
|
+
|
|
9
|
+
export type { GetRecommendedTripsProps };
|
|
10
|
+
export default GetRecommendedTripsSchema;
|
|
@@ -9,23 +9,55 @@ interface Coordinates {
|
|
|
9
9
|
|
|
10
10
|
// Add state to store coordinates
|
|
11
11
|
let currentCoordinates: Coordinates | null = null;
|
|
12
|
+
|
|
13
|
+
// Constants for local storage keys and time limits
|
|
14
|
+
const GEOLOCATION_PERMISSION_KEY = 'geolocationPermissionTimestamp';
|
|
15
|
+
const SEVEN_DAYS_IN_MS = 7 * 24 * 60 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Checks if the geolocation permission timestamp is still valid.
|
|
19
|
+
* @param {string | null} lastPermissionTimestamp - The last permission timestamp from local storage.
|
|
20
|
+
* @returns {boolean} True if the timestamp is valid, false otherwise.
|
|
21
|
+
*/
|
|
22
|
+
function isPermissionTimestampValid(
|
|
23
|
+
lastPermissionTimestamp: string | null,
|
|
24
|
+
): boolean {
|
|
25
|
+
if (!lastPermissionTimestamp) return false;
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
return now - parseInt(lastPermissionTimestamp, 10) < SEVEN_DAYS_IN_MS;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Updates the geolocation permission timestamp in local storage.
|
|
32
|
+
* @param {number} timestamp - The current timestamp to store.
|
|
33
|
+
*/
|
|
34
|
+
function updatePermissionTimestamp(timestamp: number): void {
|
|
35
|
+
localStorage.setItem(GEOLOCATION_PERMISSION_KEY, timestamp.toString());
|
|
36
|
+
}
|
|
37
|
+
|
|
12
38
|
/**
|
|
13
39
|
* Initializes geolocation service and retrieves user coordinates
|
|
14
40
|
* @returns {Promise<Coordinates | null>} Promise resolving to coordinates or null if geolocation fails/is denied
|
|
15
41
|
*/
|
|
16
42
|
function init(): Promise<Coordinates | null> {
|
|
17
43
|
return new Promise((resolve) => {
|
|
18
|
-
|
|
44
|
+
const lastPermissionTimestamp = localStorage.getItem(
|
|
45
|
+
GEOLOCATION_PERMISSION_KEY,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (isPermissionTimestampValid(lastPermissionTimestamp)) {
|
|
19
49
|
resolve(currentCoordinates);
|
|
20
50
|
return;
|
|
21
51
|
}
|
|
22
52
|
|
|
23
53
|
const GEOLOCATION_TRACK_EVENT = 'Geolocation Requested';
|
|
54
|
+
const now = Date.now();
|
|
24
55
|
|
|
25
56
|
navigator.geolocation.getCurrentPosition(
|
|
26
57
|
(geolocation) => {
|
|
27
58
|
const { latitude: lat, longitude: long } = geolocation.coords;
|
|
28
59
|
currentCoordinates = { lat, long };
|
|
60
|
+
updatePermissionTimestamp(now);
|
|
29
61
|
if (isTrackerReady()) {
|
|
30
62
|
trackCustomEvent(GEOLOCATION_TRACK_EVENT, {
|
|
31
63
|
geolocationAccepted: true,
|
|
@@ -34,6 +66,7 @@ function init(): Promise<Coordinates | null> {
|
|
|
34
66
|
resolve(currentCoordinates);
|
|
35
67
|
},
|
|
36
68
|
() => {
|
|
69
|
+
updatePermissionTimestamp(now);
|
|
37
70
|
if (isTrackerReady()) {
|
|
38
71
|
trackCustomEvent(GEOLOCATION_TRACK_EVENT, {
|
|
39
72
|
geolocationAccepted: false,
|