@bytem/bytem-tracker-app 0.0.6 → 0.0.8
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 +4 -48
- package/dist/src/BytemTracker.d.ts +129 -8
- package/dist/src/BytemTracker.js +526 -87
- package/dist/src/types.d.ts +36 -0
- package/dist/src/types.js +13 -0
- package/dist/test/BytemTracker.test.js +231 -57
- package/dist/test/debug.test.js +37 -5
- package/dist/test/setup.js +1 -0
- package/package.json +1 -1
package/dist/src/types.d.ts
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for initializing the BytemTracker.
|
|
3
|
+
*/
|
|
1
4
|
export interface TrackerConfig {
|
|
5
|
+
/** The unique application ID provided by Bytem. */
|
|
2
6
|
appId: string;
|
|
7
|
+
/** Custom endpoint URL for tracking requests. */
|
|
3
8
|
endpoint?: string;
|
|
9
|
+
/** Custom path for the API endpoint (default: '/i'). */
|
|
4
10
|
path?: string;
|
|
11
|
+
/** Enable debug logging for development. */
|
|
5
12
|
debug?: boolean;
|
|
13
|
+
/** Optional: Manually set a visitor ID. */
|
|
6
14
|
visitorId?: string;
|
|
15
|
+
/** Optional: Manually set a device ID. */
|
|
16
|
+
deviceId?: string;
|
|
17
|
+
/** Optional: App scheme for domain field in tracking. */
|
|
18
|
+
appScheme?: string;
|
|
7
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Predefined event keys used internally by the SDK.
|
|
22
|
+
*/
|
|
23
|
+
export declare const BytemEventKeys: {
|
|
24
|
+
nps: string;
|
|
25
|
+
survey: string;
|
|
26
|
+
starRating: string;
|
|
27
|
+
view: string;
|
|
28
|
+
orientation: string;
|
|
29
|
+
pushAction: string;
|
|
30
|
+
action: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Structure containing device information collected by the SDK.
|
|
34
|
+
*/
|
|
8
35
|
export interface DeviceInfo {
|
|
9
36
|
platform: string;
|
|
10
37
|
os: string;
|
|
@@ -33,9 +60,15 @@ export interface BaseParams {
|
|
|
33
60
|
screen_height: number;
|
|
34
61
|
user_agent: string;
|
|
35
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Arbitrary key-value pairs for user properties.
|
|
65
|
+
*/
|
|
36
66
|
export interface UserTraits {
|
|
37
67
|
[key: string]: any;
|
|
38
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Represents a product in the system.
|
|
71
|
+
*/
|
|
39
72
|
export interface Product {
|
|
40
73
|
productId: string;
|
|
41
74
|
name?: string;
|
|
@@ -44,6 +77,9 @@ export interface Product {
|
|
|
44
77
|
category?: string;
|
|
45
78
|
quantity?: number;
|
|
46
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Represents an order (checkout or payment).
|
|
82
|
+
*/
|
|
47
83
|
export interface Order {
|
|
48
84
|
orderId: string;
|
|
49
85
|
total: number;
|
package/dist/src/types.js
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BytemEventKeys = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Predefined event keys used internally by the SDK.
|
|
6
|
+
*/
|
|
7
|
+
exports.BytemEventKeys = {
|
|
8
|
+
nps: "[CLY]_nps",
|
|
9
|
+
survey: "[CLY]_survey",
|
|
10
|
+
starRating: "[CLY]_star_rating",
|
|
11
|
+
view: "[CLY]_view",
|
|
12
|
+
orientation: "[CLY]_orientation",
|
|
13
|
+
pushAction: "[CLY]_push_action",
|
|
14
|
+
action: "[CLY]_action",
|
|
15
|
+
};
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const BytemTracker_1 = __importDefault(require("../src/BytemTracker"));
|
|
7
7
|
const storage_1 = require("../src/core/storage");
|
|
8
8
|
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
9
|
+
const types_1 = require("../src/types");
|
|
9
10
|
describe('BytemTracker SDK', () => {
|
|
10
11
|
const mockConfig = {
|
|
11
12
|
appId: 'test-app-id',
|
|
@@ -14,71 +15,244 @@ describe('BytemTracker SDK', () => {
|
|
|
14
15
|
};
|
|
15
16
|
beforeEach(() => {
|
|
16
17
|
jest.clearAllMocks();
|
|
17
|
-
// Reset singleton instance manually
|
|
18
|
-
// @ts-ignore
|
|
19
|
-
BytemTracker_1.default.instance = undefined;
|
|
18
|
+
// Reset singleton instance state manually
|
|
20
19
|
// @ts-ignore
|
|
21
20
|
BytemTracker_1.default.isInitialized = false;
|
|
22
21
|
// @ts-ignore
|
|
23
|
-
BytemTracker_1.default.
|
|
22
|
+
BytemTracker_1.default.appKey = null;
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
BytemTracker_1.default.baseUrl = 'https://tracking.server.bytecon.com';
|
|
24
25
|
// @ts-ignore
|
|
25
26
|
BytemTracker_1.default.visitorId = null;
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
BytemTracker_1.default.sessionStarted = false;
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
BytemTracker_1.default.lastBeat = null;
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
BytemTracker_1.default.trackTime = true;
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
BytemTracker_1.default.storedDuration = 0;
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
BytemTracker_1.default.lastViewTime = 0;
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
BytemTracker_1.default.lastViewStoredDuration = 0;
|
|
39
|
+
// Clear storage mocks
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
async_storage_1.default.clear();
|
|
26
42
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
describe('Initialization', () => {
|
|
44
|
+
it('should initialize correctly', async () => {
|
|
45
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
46
|
+
// Verify Storage was checked for Visitor ID
|
|
47
|
+
expect(async_storage_1.default.getItem).toHaveBeenCalledWith(storage_1.StorageKeys.VISITOR_ID);
|
|
48
|
+
// Verify Session was initialized (checks storage)
|
|
49
|
+
expect(async_storage_1.default.getItem).toHaveBeenCalledWith(`${mockConfig.appId}/cly_session`);
|
|
50
|
+
});
|
|
51
|
+
it('should use default endpoint when not provided', async () => {
|
|
52
|
+
await BytemTracker_1.default.init({
|
|
53
|
+
appId: 'test-app-id',
|
|
54
|
+
// endpoint is omitted
|
|
55
|
+
debug: true,
|
|
56
|
+
});
|
|
57
|
+
await BytemTracker_1.default.trackEvent('test_event', {});
|
|
58
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
59
|
+
const calls = global.fetch.mock.calls;
|
|
60
|
+
// Check any call, they should all use the default endpoint
|
|
61
|
+
const url = calls[0][0];
|
|
62
|
+
expect(url).toBe('https://tracking.server.bytecon.com/i');
|
|
63
|
+
});
|
|
64
|
+
it('should override endpoint path when path is configured', async () => {
|
|
65
|
+
await BytemTracker_1.default.init({
|
|
66
|
+
appId: 'test-app-id-2',
|
|
67
|
+
endpoint: 'https://api.example.com/track',
|
|
68
|
+
path: '/collect',
|
|
69
|
+
debug: true,
|
|
70
|
+
});
|
|
71
|
+
await BytemTracker_1.default.trackEvent('test_event', {});
|
|
72
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
73
|
+
const calls = global.fetch.mock.calls;
|
|
74
|
+
const url = calls[0][0];
|
|
75
|
+
expect(url).toBe('https://api.example.com/track/collect');
|
|
76
|
+
});
|
|
33
77
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
describe('Event Tracking', () => {
|
|
79
|
+
beforeEach(async () => {
|
|
80
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
81
|
+
});
|
|
82
|
+
it('should track a simple event', async () => {
|
|
83
|
+
const eventName = 'test_event';
|
|
84
|
+
const segmentation = { foo: 'bar' };
|
|
85
|
+
await BytemTracker_1.default.trackEvent(eventName, segmentation);
|
|
86
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
87
|
+
const calls = global.fetch.mock.calls;
|
|
88
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
89
|
+
expect(eventCall).toBeDefined();
|
|
90
|
+
const options = eventCall[1];
|
|
91
|
+
const body = options.body;
|
|
92
|
+
expect(body).toContain(`app_key=${mockConfig.appId}`);
|
|
93
|
+
expect(body).toContain('events=%5B%7B%22key%22%3A%22test_event%22');
|
|
94
|
+
});
|
|
95
|
+
it('should track event with count and sum', async () => {
|
|
96
|
+
const eventName = 'purchase';
|
|
97
|
+
const count = 3;
|
|
98
|
+
const sum = 99.99;
|
|
99
|
+
const segmentation = { item: 'apple' };
|
|
100
|
+
await BytemTracker_1.default.trackEvent(eventName, segmentation, count, sum);
|
|
101
|
+
const calls = global.fetch.mock.calls;
|
|
102
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
103
|
+
const body = decodeURIComponent(eventCall[1].body);
|
|
104
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
105
|
+
expect(events[0]).toMatchObject({
|
|
106
|
+
key: eventName,
|
|
107
|
+
count: count,
|
|
108
|
+
sum: sum,
|
|
109
|
+
segmentation: segmentation
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
it('should track event with duration', async () => {
|
|
113
|
+
const eventName = 'video_watched';
|
|
114
|
+
const duration = 120; // seconds
|
|
115
|
+
await BytemTracker_1.default.trackEvent(eventName, {}, 1, undefined, duration);
|
|
116
|
+
const calls = global.fetch.mock.calls;
|
|
117
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
118
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
119
|
+
expect(events[0].dur).toBe(duration);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('Page View Tracking', () => {
|
|
123
|
+
beforeEach(async () => {
|
|
124
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
125
|
+
});
|
|
126
|
+
it('should track page view', async () => {
|
|
127
|
+
const pageName = '/home';
|
|
128
|
+
await BytemTracker_1.default.trackPageview(pageName);
|
|
129
|
+
const calls = global.fetch.mock.calls;
|
|
130
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes(`%22key%22%3A%22${encodeURIComponent(types_1.BytemEventKeys.view)}%22`));
|
|
131
|
+
expect(eventCall).toBeDefined();
|
|
132
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
133
|
+
expect(events[0].segmentation).toMatchObject({
|
|
134
|
+
name: pageName,
|
|
135
|
+
current: pageName,
|
|
136
|
+
referrer: ''
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
it('should track page view with referrer', async () => {
|
|
140
|
+
const pageName = '/product/123';
|
|
141
|
+
const referrer = '/home';
|
|
142
|
+
await BytemTracker_1.default.trackPageview(pageName, referrer);
|
|
143
|
+
const calls = global.fetch.mock.calls;
|
|
144
|
+
// Get the last call which should be the page view
|
|
145
|
+
const eventCall = calls[calls.length - 1];
|
|
146
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
147
|
+
expect(events[0].segmentation).toMatchObject({
|
|
148
|
+
name: pageName,
|
|
149
|
+
current: pageName,
|
|
150
|
+
referrer: referrer
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe('User Tracking', () => {
|
|
155
|
+
beforeEach(async () => {
|
|
156
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
157
|
+
});
|
|
158
|
+
it('should track user details', async () => {
|
|
159
|
+
const userId = 'user_123';
|
|
160
|
+
const userTraits = {
|
|
161
|
+
name: 'John Doe',
|
|
162
|
+
email: 'john@example.com',
|
|
163
|
+
custom: {
|
|
164
|
+
plan: 'premium'
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
await BytemTracker_1.default.trackUser(userId, userTraits);
|
|
168
|
+
const calls = global.fetch.mock.calls;
|
|
169
|
+
const userCall = calls.find(call => call[1].body && call[1].body.includes('user_details='));
|
|
170
|
+
expect(userCall).toBeDefined();
|
|
171
|
+
const userDetails = JSON.parse(new URLSearchParams(userCall[1].body).get('user_details') || '{}');
|
|
172
|
+
expect(userDetails).toMatchObject({
|
|
173
|
+
name: 'John Doe',
|
|
174
|
+
email: 'john@example.com',
|
|
175
|
+
custom: {
|
|
176
|
+
plan: 'premium',
|
|
177
|
+
user_id: userId
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
56
181
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
182
|
+
describe('Session Management', () => {
|
|
183
|
+
beforeEach(async () => {
|
|
184
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
185
|
+
});
|
|
186
|
+
it('should track session start', async () => {
|
|
187
|
+
await BytemTracker_1.default.trackSessions();
|
|
188
|
+
const calls = global.fetch.mock.calls;
|
|
189
|
+
const sessionCall = calls.find(call => call[1].body && call[1].body.includes('begin_session=1'));
|
|
190
|
+
expect(sessionCall).toBeDefined();
|
|
191
|
+
});
|
|
192
|
+
it('should track session end', async () => {
|
|
193
|
+
// First start session
|
|
194
|
+
await BytemTracker_1.default.trackSessions();
|
|
195
|
+
// Then end session
|
|
196
|
+
await BytemTracker_1.default.endSession(undefined, true); // force=true to bypass cookie check for test
|
|
197
|
+
const calls = global.fetch.mock.calls;
|
|
198
|
+
const endSessionCall = calls.find(call => call[1].body && call[1].body.includes('end_session=1'));
|
|
199
|
+
expect(endSessionCall).toBeDefined();
|
|
200
|
+
const body = new URLSearchParams(endSessionCall[1].body);
|
|
201
|
+
expect(body.has('session_duration')).toBe(true);
|
|
202
|
+
});
|
|
69
203
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
204
|
+
describe('E-commerce Tracking', () => {
|
|
205
|
+
beforeEach(async () => {
|
|
206
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
207
|
+
});
|
|
208
|
+
it('should track checkout order', async () => {
|
|
209
|
+
const order = {
|
|
210
|
+
orderId: 'order_123',
|
|
211
|
+
total: 99.99,
|
|
212
|
+
currency: 'USD',
|
|
213
|
+
products: [
|
|
214
|
+
{ productId: 'prod_1', price: 50, quantity: 1 },
|
|
215
|
+
{ productId: 'prod_2', price: 49.99, quantity: 1 }
|
|
216
|
+
]
|
|
217
|
+
};
|
|
218
|
+
await BytemTracker_1.default.trackCheckOutOrder(order);
|
|
219
|
+
const calls = global.fetch.mock.calls;
|
|
220
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
221
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
222
|
+
expect(events[0].key).toBe('check_out_order');
|
|
223
|
+
expect(events[0].segmentation).toMatchObject({
|
|
224
|
+
order_id: 'order_123',
|
|
225
|
+
total: 99.99,
|
|
226
|
+
currency: 'USD',
|
|
227
|
+
gids: ['prod_1', 'prod_2'],
|
|
228
|
+
prices: [50, 49.99],
|
|
229
|
+
quantity: [1, 1]
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
it('should track pay order', async () => {
|
|
233
|
+
const order = {
|
|
234
|
+
orderId: 'order_123',
|
|
235
|
+
total: 99.99,
|
|
236
|
+
currency: 'USD',
|
|
237
|
+
products: [
|
|
238
|
+
{ productId: 'prod_1', name: 'Product 1', price: 50, quantity: 1 },
|
|
239
|
+
{ productId: 'prod_2', name: 'Product 2', price: 49.99, quantity: 2 }
|
|
240
|
+
]
|
|
241
|
+
};
|
|
242
|
+
await BytemTracker_1.default.trackPayOrder(order);
|
|
243
|
+
const calls = global.fetch.mock.calls;
|
|
244
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
245
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
246
|
+
expect(events[0].key).toBe('pay_order');
|
|
247
|
+
expect(events[0].segmentation).toMatchObject({
|
|
248
|
+
order_id: 'order_123',
|
|
249
|
+
total: 99.99,
|
|
250
|
+
currency: 'USD',
|
|
251
|
+
goods: [
|
|
252
|
+
{ gid: 'prod_1', name: 'Product 1', price: 50, quantity: 1 },
|
|
253
|
+
{ gid: 'prod_2', name: 'Product 2', price: 49.99, quantity: 2 }
|
|
254
|
+
]
|
|
255
|
+
});
|
|
256
|
+
});
|
|
83
257
|
});
|
|
84
258
|
});
|
package/dist/test/debug.test.js
CHANGED
|
@@ -13,7 +13,22 @@ describe('Debug Scenarios', () => {
|
|
|
13
13
|
};
|
|
14
14
|
beforeEach(async () => {
|
|
15
15
|
jest.clearAllMocks();
|
|
16
|
+
// Reset singleton instance state manually
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
BytemTracker_1.default.isInitialized = false;
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
BytemTracker_1.default.appKey = null;
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
BytemTracker_1.default.baseUrl = 'https://tracking.server.bytecon.com';
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
BytemTracker_1.default.visitorId = null;
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
BytemTracker_1.default.sessionStarted = false;
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
BytemTracker_1.default.lastBeat = null;
|
|
16
29
|
// Simulate fresh start
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
async_storage_1.default.clear();
|
|
17
32
|
async_storage_1.default.getItem.mockResolvedValue(null);
|
|
18
33
|
await BytemTracker_1.default.init(mockConfig);
|
|
19
34
|
});
|
|
@@ -48,11 +63,28 @@ describe('Debug Scenarios', () => {
|
|
|
48
63
|
});
|
|
49
64
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
50
65
|
// Assertions to verify flow
|
|
51
|
-
|
|
66
|
+
// Expect 4 calls: begin_session, view_product, user_details, check_out_order
|
|
67
|
+
expect(global.fetch).toHaveBeenCalledTimes(4);
|
|
52
68
|
const calls = global.fetch.mock.calls;
|
|
53
|
-
// Check
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
// Check Request Types
|
|
70
|
+
const requestTypes = calls.map((call) => {
|
|
71
|
+
const body = call[1].body;
|
|
72
|
+
if (body.includes('begin_session=1'))
|
|
73
|
+
return 'begin_session';
|
|
74
|
+
if (body.includes('user_details='))
|
|
75
|
+
return 'identify';
|
|
76
|
+
if (body.includes('events=')) {
|
|
77
|
+
// Extract event key
|
|
78
|
+
const match = body.match(/events=(.*?)($|&)/);
|
|
79
|
+
if (match) {
|
|
80
|
+
const eventsJson = decodeURIComponent(match[1]);
|
|
81
|
+
const events = JSON.parse(eventsJson);
|
|
82
|
+
return events[0].key;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return 'unknown';
|
|
86
|
+
});
|
|
87
|
+
console.log('>>> Captured Requests:', requestTypes);
|
|
88
|
+
expect(requestTypes).toEqual(['begin_session', 'view_product', 'identify', 'check_out_order']);
|
|
57
89
|
});
|
|
58
90
|
});
|
package/dist/test/setup.js
CHANGED