@reservamos/browser-analytics 1.1.2 → 1.1.3-alpha.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/README.md +35 -0
- package/dist/browser-analytics.cjs +3 -3
- package/dist/browser-analytics.cjs.map +1 -1
- package/dist/browser-analytics.d.ts +7 -0
- package/dist/browser-analytics.esm.js +28 -6
- 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 +1 -1
- package/src/events/identify/identify.test.ts +102 -0
- package/src/events/identify/identify.ts +3 -0
- package/src/init.ts +4 -0
- package/src/services/config.test.ts +39 -0
- package/src/services/config.ts +16 -1
- package/src/services/validator/validator.test.ts +34 -1
- package/src/services/validator/validator.ts +5 -0
package/package.json
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const getCoreAPIConfigMock = vi.fn();
|
|
4
|
+
const getCoordinatesMock = vi.fn();
|
|
5
|
+
const createAnonymousProfileMock = vi.fn();
|
|
6
|
+
const isReadyMock = vi.fn();
|
|
7
|
+
|
|
8
|
+
vi.mock('@/services/config', () => ({
|
|
9
|
+
default: {
|
|
10
|
+
getCoreAPIConfig: getCoreAPIConfigMock,
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@/services/geolocation', () => ({
|
|
15
|
+
default: {
|
|
16
|
+
getCoordinates: getCoordinatesMock,
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock('@/profiles/createAnonymousProfile', () => ({
|
|
21
|
+
default: createAnonymousProfileMock,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock('@/services/mixpanel', () => ({
|
|
25
|
+
default: {
|
|
26
|
+
isReady: isReadyMock,
|
|
27
|
+
identify: vi.fn(),
|
|
28
|
+
attachProperty: vi.fn(),
|
|
29
|
+
getMixpanelDistinctId: vi.fn(),
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock('@/services/fingerprint', () => ({
|
|
34
|
+
default: {
|
|
35
|
+
getFingerprint: vi.fn().mockResolvedValue(null),
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
vi.mock('@/services/validator', () => ({
|
|
40
|
+
default: {
|
|
41
|
+
parseIdentifyProps: vi.fn(),
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
vi.mock('@/track', () => ({
|
|
46
|
+
tryTrackEvent: async (callback: () => Promise<void>) => callback(),
|
|
47
|
+
trackEventError: vi.fn(),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
describe('identify datalake request', () => {
|
|
51
|
+
const fetchMock = vi.fn().mockResolvedValue({ ok: true });
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
vi.resetModules();
|
|
55
|
+
fetchMock.mockClear();
|
|
56
|
+
getCoordinatesMock.mockReset();
|
|
57
|
+
createAnonymousProfileMock.mockReset();
|
|
58
|
+
getCoreAPIConfigMock.mockReset();
|
|
59
|
+
isReadyMock.mockReturnValue(true);
|
|
60
|
+
|
|
61
|
+
createAnonymousProfileMock.mockResolvedValue(undefined);
|
|
62
|
+
getCoordinatesMock.mockReturnValue({ lat: 19.4326, long: -99.1332 });
|
|
63
|
+
getCoreAPIConfigMock.mockReturnValue({
|
|
64
|
+
baseUrl: 'https://data-lake.reservamossaas.com/api',
|
|
65
|
+
defaultHeaders: {
|
|
66
|
+
Authorization: 'Bearer test-token',
|
|
67
|
+
Origin: 'https://example.com',
|
|
68
|
+
'x-app-name': 'reservamos-web',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
vi.restoreAllMocks();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('sends x-app-name header on datalake identify request', async () => {
|
|
80
|
+
const identify = (await import('./identify')).default;
|
|
81
|
+
|
|
82
|
+
await identify('user-123', {
|
|
83
|
+
email: 'test@example.com',
|
|
84
|
+
firstName: 'John',
|
|
85
|
+
lastName: 'Doe',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await vi.waitFor(() => {
|
|
89
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
93
|
+
'https://data-lake.reservamossaas.com/api/v1/datalake/identify',
|
|
94
|
+
expect.objectContaining({
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: expect.objectContaining({
|
|
97
|
+
'x-app-name': 'reservamos-web',
|
|
98
|
+
}),
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -75,6 +75,9 @@ const identifyWithLocation = async (
|
|
|
75
75
|
'Content-Type': 'application/json',
|
|
76
76
|
'Authorization': coreApiConfig.defaultHeaders?.Authorization || '',
|
|
77
77
|
'Origin': coreApiConfig.defaultHeaders?.Origin || '',
|
|
78
|
+
...(coreApiConfig.defaultHeaders?.['x-app-name']
|
|
79
|
+
? { 'x-app-name': coreApiConfig.defaultHeaders['x-app-name'] }
|
|
80
|
+
: {}),
|
|
78
81
|
},
|
|
79
82
|
body: JSON.stringify({
|
|
80
83
|
profile_params: {
|
package/src/init.ts
CHANGED
|
@@ -36,6 +36,7 @@ async function init(config: InitConfig) {
|
|
|
36
36
|
isSandbox = false,
|
|
37
37
|
mixpanelProxyUrl,
|
|
38
38
|
identifyProxyUrl,
|
|
39
|
+
appName,
|
|
39
40
|
} = config;
|
|
40
41
|
|
|
41
42
|
await mixpanelService.init(mixpanelToken, debug, mixpanelProxyUrl);
|
|
@@ -52,6 +53,9 @@ async function init(config: InitConfig) {
|
|
|
52
53
|
await geolocationService.init();
|
|
53
54
|
|
|
54
55
|
configService.setEnvironment(isSandbox ? 'sandbox' : 'prod');
|
|
56
|
+
if (appName) {
|
|
57
|
+
configService.setAppName(appName);
|
|
58
|
+
}
|
|
55
59
|
coreApi.setConfig(configService.getCoreAPIConfig());
|
|
56
60
|
|
|
57
61
|
// Dispatch the 'Tracker Ready' event
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('configService', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.resetModules();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('includes x-app-name in defaultHeaders after setAppName (prod)', async () => {
|
|
9
|
+
const configService = (await import('./config')).default;
|
|
10
|
+
|
|
11
|
+
configService.setAppName('my-bus-app');
|
|
12
|
+
configService.setEnvironment('prod');
|
|
13
|
+
|
|
14
|
+
const config = configService.getCoreAPIConfig();
|
|
15
|
+
|
|
16
|
+
expect(config.defaultHeaders?.['x-app-name']).toBe('my-bus-app');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('includes x-app-name in defaultHeaders after setAppName (sandbox)', async () => {
|
|
20
|
+
const configService = (await import('./config')).default;
|
|
21
|
+
|
|
22
|
+
configService.setAppName('whitelabel-portal');
|
|
23
|
+
configService.setEnvironment('sandbox');
|
|
24
|
+
|
|
25
|
+
const config = configService.getCoreAPIConfig();
|
|
26
|
+
|
|
27
|
+
expect(config.defaultHeaders?.['x-app-name']).toBe('whitelabel-portal');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('omits x-app-name from defaultHeaders when setAppName is not called', async () => {
|
|
31
|
+
const configService = (await import('./config')).default;
|
|
32
|
+
|
|
33
|
+
configService.setEnvironment('prod');
|
|
34
|
+
|
|
35
|
+
const config = configService.getCoreAPIConfig();
|
|
36
|
+
|
|
37
|
+
expect(config.defaultHeaders?.['x-app-name']).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
package/src/services/config.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ApiConfig } from '@reservamos/js-api-client/core';
|
|
|
3
3
|
type AvailableEnvironments = 'sandbox' | 'prod';
|
|
4
4
|
|
|
5
5
|
let environment: AvailableEnvironments | undefined;
|
|
6
|
+
let appName: string | undefined;
|
|
6
7
|
const origin = window.location.origin;
|
|
7
8
|
|
|
8
9
|
type CoreAPIConfig = Record<AvailableEnvironments, ApiConfig>;
|
|
@@ -28,6 +29,10 @@ function setEnvironment(env: AvailableEnvironments) {
|
|
|
28
29
|
environment = env;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function setAppName(name: string) {
|
|
33
|
+
appName = name;
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
/**
|
|
32
37
|
* Returns the core API config for the current environment.
|
|
33
38
|
* @throws {Error} Throws an error if the environment is not set.
|
|
@@ -38,11 +43,21 @@ function getCoreAPIConfig(): ApiConfig {
|
|
|
38
43
|
'Unable to get core API config, environment not set. Use configService.setEnvironment(environment) to set the environment.',
|
|
39
44
|
);
|
|
40
45
|
}
|
|
41
|
-
|
|
46
|
+
|
|
47
|
+
const config = coreAPIConfig[environment];
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...config,
|
|
51
|
+
defaultHeaders: {
|
|
52
|
+
...config.defaultHeaders,
|
|
53
|
+
...(appName ? { 'x-app-name': appName } : {}),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
const configService = {
|
|
45
59
|
setEnvironment,
|
|
60
|
+
setAppName,
|
|
46
61
|
getCoreAPIConfig,
|
|
47
62
|
};
|
|
48
63
|
|
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { customEventSchema } from '@/events/customEvent';
|
|
3
|
-
import validatorService, { eventSchemas } from './validator';
|
|
3
|
+
import validatorService, { eventSchemas, InitConfigSchema } from './validator';
|
|
4
|
+
|
|
5
|
+
describe('InitConfigSchema', () => {
|
|
6
|
+
const validConfig = {
|
|
7
|
+
mixpanelToken: 'token',
|
|
8
|
+
identificationKey: 'key',
|
|
9
|
+
appName: 'reservamos-web',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
it('accepts config without appName', () => {
|
|
13
|
+
expect(
|
|
14
|
+
InitConfigSchema.parse({
|
|
15
|
+
mixpanelToken: 'token',
|
|
16
|
+
identificationKey: 'key',
|
|
17
|
+
}),
|
|
18
|
+
).toEqual({
|
|
19
|
+
mixpanelToken: 'token',
|
|
20
|
+
identificationKey: 'key',
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('rejects empty appName', () => {
|
|
25
|
+
expect(() =>
|
|
26
|
+
InitConfigSchema.parse({
|
|
27
|
+
...validConfig,
|
|
28
|
+
appName: '',
|
|
29
|
+
}),
|
|
30
|
+
).toThrow();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('accepts valid appName', () => {
|
|
34
|
+
expect(InitConfigSchema.parse(validConfig)).toEqual(validConfig);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
4
37
|
|
|
5
38
|
describe('parseEventProps', () => {
|
|
6
39
|
/**
|
|
@@ -39,6 +39,11 @@ const InitConfigSchema = z.object({
|
|
|
39
39
|
* Optional proxy URL for identify requests.
|
|
40
40
|
*/
|
|
41
41
|
identifyProxyUrl: z.string().optional(),
|
|
42
|
+
/**
|
|
43
|
+
* Optional client platform identifier sent as the `x-app-name` header on Datalake
|
|
44
|
+
* identify and anonymous-profile requests.
|
|
45
|
+
*/
|
|
46
|
+
appName: z.string().min(1, 'App name cannot be empty').optional(),
|
|
42
47
|
});
|
|
43
48
|
|
|
44
49
|
interface CustomError {
|