@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.
@@ -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.config = null;
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
- it('should initialize correctly', async () => {
28
- await BytemTracker_1.default.init(mockConfig);
29
- // Verify Storage was checked for Visitor ID
30
- expect(async_storage_1.default.getItem).toHaveBeenCalledWith(storage_1.StorageKeys.VISITOR_ID);
31
- // Verify Session was initialized (checks storage)
32
- expect(async_storage_1.default.getItem).toHaveBeenCalledWith(storage_1.StorageKeys.SESSION_ID);
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
- it('should track an event', async () => {
35
- // Ensure initialized (singleton persists)
36
- await BytemTracker_1.default.init(mockConfig);
37
- const eventName = 'test_event';
38
- const eventParams = { foo: 'bar' };
39
- // @ts-ignore - Accessing private method for testing or just use public track methods
40
- // Since track is private, we'll use a public method like trackUser or just trust the public API
41
- // Let's use trackUser as a proxy for generic tracking
42
- BytemTracker_1.default.trackUser('user-123', { age: 25 });
43
- // Wait for async operations if any (track is async but void return in public API might hide it)
44
- // In BytemTracker.ts, trackUser calls track, which is async but not awaited by caller.
45
- // We need to wait for promises to resolve.
46
- await new Promise(resolve => setTimeout(resolve, 0));
47
- expect(global.fetch).toHaveBeenCalledTimes(1);
48
- const fetchCall = global.fetch.mock.calls[0];
49
- const url = fetchCall[0];
50
- const options = fetchCall[1];
51
- expect(url).toBe(mockConfig.endpoint);
52
- expect(options.method).toBe('POST');
53
- const body = JSON.parse(options.body);
54
- expect(body.app_id).toBe(mockConfig.appId);
55
- // expect(body.event).toBe('identify');
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
- it('should use default endpoint when not provided', async () => {
58
- await BytemTracker_1.default.init({
59
- appId: 'test-app-id',
60
- // endpoint is omitted
61
- debug: true,
62
- });
63
- BytemTracker_1.default.trackUser('user-123', { age: 25 });
64
- await new Promise(resolve => setTimeout(resolve, 0));
65
- expect(global.fetch).toHaveBeenCalledTimes(1);
66
- const fetchCall = global.fetch.mock.calls[0];
67
- const url = fetchCall[0];
68
- expect(url).toBe('https://tracking.server.bytecon.com/i');
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
- it('should override endpoint path when path is configured', async () => {
71
- await BytemTracker_1.default.init({
72
- appId: 'test-app-id-2',
73
- endpoint: 'https://api.example.com/track',
74
- path: '/collect',
75
- debug: true,
76
- });
77
- BytemTracker_1.default.trackUser('user-456', { age: 30 });
78
- await new Promise(resolve => setTimeout(resolve, 0));
79
- expect(global.fetch).toHaveBeenCalledTimes(1);
80
- const fetchCall = global.fetch.mock.calls[0];
81
- const url = fetchCall[0];
82
- expect(url).toBe('https://api.example.com/collect');
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
  });
@@ -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
- expect(global.fetch).toHaveBeenCalledTimes(3);
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 Event Types
54
- const events = calls.map((call) => JSON.parse(call[1].body).event);
55
- console.log('>>> Captured Events:', events);
56
- expect(events).toEqual(['view_product', 'identify', 'checkout_order']);
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
  });
@@ -53,4 +53,5 @@ global.fetch = jest.fn(() => Promise.resolve({
53
53
  status: 200,
54
54
  statusText: 'OK',
55
55
  json: () => Promise.resolve({}),
56
+ text: () => Promise.resolve('{"status":"success"}'),
56
57
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytem/bytem-tracker-app",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Bytem Tracker SDK for React Native",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",