@bytem/bytem-tracker-app 0.0.11 → 0.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytem/bytem-tracker-app",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Bytem Tracker SDK for React Native",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -30,8 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "uuid": "^9.0.0",
33
- "@react-native-async-storage/async-storage": "^1.19.0",
34
- "react-native-device-info": "^10.8.0"
33
+ "@react-native-async-storage/async-storage": "^1.19.0"
35
34
  },
36
35
  "devDependencies": {
37
36
  "@react-native-async-storage/async-storage": "^1.19.0",
@@ -43,7 +42,6 @@
43
42
  "jest": "^30.2.0",
44
43
  "react": "18.2.0",
45
44
  "react-native": "0.72.0",
46
- "react-native-device-info": "^10.8.0",
47
45
  "ts-jest": "^29.4.6",
48
46
  "typescript": "^5.0.0"
49
47
  },
@@ -31,7 +31,7 @@ declare class BytemTracker {
31
31
  * @returns A promise that resolves when initialization is complete.
32
32
  */
33
33
  init(config: TrackerConfig): Promise<void>;
34
- private ensureInitialized;
34
+ private checkInitialized;
35
35
  /**
36
36
  * Begins a new user session.
37
37
  * Handles session cookie logic to prevent excessive session starts if one is already active.
@@ -7,7 +7,7 @@ const package_json_1 = require("../package.json");
7
7
  class BytemTracker {
8
8
  constructor() {
9
9
  // SDK Constants
10
- this.SDK_NAME = 'react_native_bytem';
10
+ this.SDK_NAME = 'bytem_tracker_app';
11
11
  this.SDK_VERSION = package_json_1.version; // Should match package.json
12
12
  this.DEFAULT_ENDPOINT = 'https://tracking.server.bytecon.com';
13
13
  this.DEFAULT_API_PATH = '/i';
@@ -44,78 +44,87 @@ class BytemTracker {
44
44
  * @returns A promise that resolves when initialization is complete.
45
45
  */
46
46
  async init(config) {
47
- if (this.isInitialized) {
48
- console.warn('[BytemTracker] Already initialized');
49
- return;
50
- }
51
- this.appKey = config.appId;
52
- if (config.endpoint) {
53
- this.baseUrl = config.endpoint;
54
- }
55
- this.debug = !!config.debug;
56
- this.appScheme = config.appScheme || null;
57
- if (config.path) {
58
- this.apiPath = config.path.startsWith('/') ? config.path : '/' + config.path;
59
- }
60
- // Initialize Visitor ID logic:
61
- // 1. If provided in config, use it.
62
- // 2. If not, try to retrieve from storage.
63
- // 3. If not in storage, generate a new UUID.
64
- if (config.visitorId) {
65
- this.visitorId = config.visitorId;
66
- await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
67
- if (this.debug)
68
- console.log(`[BytemTracker] Using provided visitor ID: ${this.visitorId}`);
69
- }
70
- else {
71
- this.visitorId = await (0, storage_1.getItem)(storage_1.StorageKeys.VISITOR_ID);
72
- if (!this.visitorId) {
73
- this.visitorId = (0, device_1.generateUUID)();
47
+ try {
48
+ if (this.isInitialized) {
49
+ console.warn('[BytemTracker] Already initialized');
50
+ return;
51
+ }
52
+ this.appKey = config.appId;
53
+ if (config.endpoint) {
54
+ this.baseUrl = config.endpoint;
55
+ }
56
+ this.debug = !!config.debug;
57
+ this.appScheme = config.appScheme || null;
58
+ if (config.path) {
59
+ this.apiPath = config.path.startsWith('/') ? config.path : '/' + config.path;
60
+ }
61
+ // Initialize Visitor ID logic:
62
+ // 1. If provided in config, use it.
63
+ // 2. If not, try to retrieve from storage.
64
+ // 3. If not in storage, generate a new UUID.
65
+ if (config.visitorId) {
66
+ this.visitorId = config.visitorId;
74
67
  await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
75
68
  if (this.debug)
76
- console.log(`[BytemTracker] Generated new visitor ID: ${this.visitorId}`);
69
+ console.log(`[BytemTracker] Using provided visitor ID: ${this.visitorId}`);
77
70
  }
78
71
  else {
72
+ this.visitorId = await (0, storage_1.getItem)(storage_1.StorageKeys.VISITOR_ID);
73
+ if (!this.visitorId) {
74
+ this.visitorId = (0, device_1.generateUUID)();
75
+ await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
76
+ if (this.debug)
77
+ console.log(`[BytemTracker] Generated new visitor ID: ${this.visitorId}`);
78
+ }
79
+ else {
80
+ if (this.debug)
81
+ console.log(`[BytemTracker] Restored visitor ID: ${this.visitorId}`);
82
+ }
83
+ }
84
+ // Initialize Device ID type logic:
85
+ // 0: Developer supplied
86
+ // 1: SDK generated (default)
87
+ if (config.deviceId) {
88
+ this.deviceIdType = 0; // Developer set
89
+ await (0, storage_1.setItem)(storage_1.StorageKeys.DEVICE_ID, config.deviceId);
79
90
  if (this.debug)
80
- console.log(`[BytemTracker] Restored visitor ID: ${this.visitorId}`);
91
+ console.log(`[BytemTracker] Using developer-set device ID: ${config.deviceId}`);
81
92
  }
82
- }
83
- // Initialize Device ID type logic:
84
- // 0: Developer supplied
85
- // 1: SDK generated (default)
86
- if (config.deviceId) {
87
- this.deviceIdType = 0; // Developer set
88
- await (0, storage_1.setItem)(storage_1.StorageKeys.DEVICE_ID, config.deviceId);
89
- if (this.debug)
90
- console.log(`[BytemTracker] Using developer-set device ID: ${config.deviceId}`);
91
- }
92
- else {
93
- this.deviceIdType = 1; // Auto generated (using visitor ID or platform ID logic)
94
- }
95
- this.isInitialized = true;
96
- if (this.debug) {
97
- console.log('[BytemTracker] Initialized ✅');
98
- console.log(` app_key: ${this.appKey}`);
99
- console.log(` base_url: ${this.baseUrl}`);
100
- console.log(` visitor_id: ${this.visitorId}`);
101
- if (this.appScheme)
102
- console.log(` app_scheme: ${this.appScheme}`);
103
- }
104
- // Attempt to begin a session automatically on init
105
- try {
106
- await this.beginSession();
107
- }
108
- catch (e) {
93
+ else {
94
+ this.deviceIdType = 1; // Auto generated (using visitor ID or platform ID logic)
95
+ }
96
+ this.isInitialized = true;
109
97
  if (this.debug) {
110
- console.warn('[BytemTracker] Failed to begin session during init:', e);
98
+ console.log('[BytemTracker] Initialized ');
99
+ console.log(` app_key: ${this.appKey}`);
100
+ console.log(` base_url: ${this.baseUrl}`);
101
+ console.log(` visitor_id: ${this.visitorId}`);
102
+ if (this.appScheme)
103
+ console.log(` app_scheme: ${this.appScheme}`);
104
+ }
105
+ // Attempt to begin a session automatically on init
106
+ try {
107
+ await this.beginSession();
108
+ }
109
+ catch (e) {
110
+ if (this.debug) {
111
+ console.warn('[BytemTracker] Failed to begin session during init:', e);
112
+ }
113
+ this.sessionStarted = false;
111
114
  }
112
- this.sessionStarted = false;
115
+ }
116
+ catch (e) {
117
+ if (this.debug)
118
+ console.error('[BytemTracker] Init failed:', e);
113
119
  }
114
120
  }
115
- ensureInitialized() {
121
+ checkInitialized() {
116
122
  if (!this.isInitialized) {
117
- throw new Error('[BytemTracker] Not initialized. Call await init() first.');
123
+ if (this.debug)
124
+ console.warn('[BytemTracker] Not initialized. Call await init() first.');
125
+ return false;
118
126
  }
127
+ return true;
119
128
  }
120
129
  /**
121
130
  * Begins a new user session.
@@ -124,7 +133,8 @@ class BytemTracker {
124
133
  * @param force - If true, forces a new session start request even if the session cookie is valid.
125
134
  */
126
135
  async beginSession(force = false) {
127
- this.ensureInitialized();
136
+ if (!this.checkInitialized())
137
+ return;
128
138
  if (this.sessionStarted) {
129
139
  if (this.debug)
130
140
  console.log('[BytemTracker] Session already started, skipping');
@@ -170,18 +180,25 @@ class BytemTracker {
170
180
  * @param sec - The duration in seconds to report.
171
181
  */
172
182
  async sessionDuration(sec) {
173
- this.ensureInitialized();
174
- if (!this.sessionStarted) {
183
+ try {
184
+ if (!this.checkInitialized())
185
+ return;
186
+ if (!this.sessionStarted) {
187
+ if (this.debug)
188
+ console.log('[BytemTracker] Session not started, skipping session_duration');
189
+ return;
190
+ }
191
+ const deviceId = await this.getRealDeviceId();
192
+ const body = await this.buildBaseRequestParams(deviceId);
193
+ body['session_duration'] = sec.toString();
194
+ await this.sendRequest(this.apiPath, body, 'Session Duration');
195
+ this.lastBeat = Date.now();
196
+ await this.extendSession();
197
+ }
198
+ catch (e) {
175
199
  if (this.debug)
176
- console.log('[BytemTracker] Session not started, skipping session_duration');
177
- return;
200
+ console.error('[BytemTracker] Error in sessionDuration:', e);
178
201
  }
179
- const deviceId = await this.getRealDeviceId();
180
- const body = await this.buildBaseRequestParams(deviceId);
181
- body['session_duration'] = sec.toString();
182
- await this.sendRequest(this.apiPath, body, 'Session Duration');
183
- this.lastBeat = Date.now();
184
- await this.extendSession();
185
202
  }
186
203
  /**
187
204
  * Ends the current user session.
@@ -190,92 +207,118 @@ class BytemTracker {
190
207
  * @param force - If true, forces the end session request even if using session cookies.
191
208
  */
192
209
  async endSession(sec, force = false) {
193
- this.ensureInitialized();
194
- if (!this.sessionStarted) {
210
+ try {
211
+ if (!this.checkInitialized())
212
+ return;
213
+ if (!this.sessionStarted) {
214
+ if (this.debug)
215
+ console.log('[BytemTracker] Session not started, skipping end_session');
216
+ return;
217
+ }
218
+ const currentTimestamp = Date.now();
219
+ const sessionSec = sec !== null && sec !== void 0 ? sec : (this.lastBeat ? (currentTimestamp - this.lastBeat) / 1000 : 0);
195
220
  if (this.debug)
196
- console.log('[BytemTracker] Session not started, skipping end_session');
197
- return;
198
- }
199
- const currentTimestamp = Date.now();
200
- const sessionSec = sec !== null && sec !== void 0 ? sec : (this.lastBeat ? (currentTimestamp - this.lastBeat) / 1000 : 0);
201
- if (this.debug)
202
- console.log(`[BytemTracker] Ending session with duration: ${sessionSec} seconds`);
203
- if (this.useSessionCookie && !force) {
204
- await this.sessionDuration(sessionSec);
221
+ console.log(`[BytemTracker] Ending session with duration: ${sessionSec} seconds`);
222
+ if (this.useSessionCookie && !force) {
223
+ await this.sessionDuration(sessionSec);
224
+ this.sessionStarted = false;
225
+ return;
226
+ }
227
+ const deviceId = await this.getRealDeviceId();
228
+ const body = await this.buildBaseRequestParams(deviceId);
229
+ body['end_session'] = '1';
230
+ body['session_duration'] = sessionSec.toString();
231
+ await this.sendRequest(this.apiPath, body, 'End Session');
205
232
  this.sessionStarted = false;
206
- return;
207
233
  }
208
- const deviceId = await this.getRealDeviceId();
209
- const body = await this.buildBaseRequestParams(deviceId);
210
- body['end_session'] = '1';
211
- body['session_duration'] = sessionSec.toString();
212
- await this.sendRequest(this.apiPath, body, 'End Session');
213
- this.sessionStarted = false;
234
+ catch (e) {
235
+ if (this.debug)
236
+ console.error('[BytemTracker] Error in endSession:', e);
237
+ }
214
238
  }
215
239
  /**
216
240
  * Manually tracks or extends a session.
217
241
  * If a session is active, it reports duration. If not, it begins a new session.
218
242
  */
219
243
  async trackSessions() {
220
- this.ensureInitialized();
221
- if (this.debug)
222
- console.log('[BytemTracker] trackSessions called');
223
- if (this.sessionStarted && this.lastBeat) {
224
- const duration = (Date.now() - this.lastBeat) / 1000;
225
- if (duration > 0) {
226
- if (this.debug)
227
- console.log(`[BytemTracker] Session already started, sending session_duration: ${duration}s`);
228
- await this.sessionDuration(duration);
244
+ try {
245
+ if (!this.checkInitialized())
246
+ return;
247
+ if (this.debug)
248
+ console.log('[BytemTracker] trackSessions called');
249
+ if (this.sessionStarted && this.lastBeat) {
250
+ const duration = (Date.now() - this.lastBeat) / 1000;
251
+ if (duration > 0) {
252
+ if (this.debug)
253
+ console.log(`[BytemTracker] Session already started, sending session_duration: ${duration}s`);
254
+ await this.sessionDuration(duration);
255
+ }
256
+ }
257
+ else {
258
+ await this.beginSession();
229
259
  }
260
+ this.startTime();
261
+ if (this.debug)
262
+ console.log('[BytemTracker] Session tracking started');
230
263
  }
231
- else {
232
- await this.beginSession();
264
+ catch (e) {
265
+ if (this.debug)
266
+ console.error('[BytemTracker] Error in trackSessions:', e);
233
267
  }
234
- this.startTime();
235
- if (this.debug)
236
- console.log('[BytemTracker] Session tracking started');
237
268
  }
238
269
  /**
239
270
  * Resumes time tracking for session duration.
240
271
  */
241
272
  startTime() {
242
- if (!this.trackTime) {
243
- this.trackTime = true;
244
- const now = Date.now();
245
- if (this.lastBeat && this.storedDuration > 0) {
246
- this.lastBeat = now - this.storedDuration;
247
- this.storedDuration = 0;
248
- }
249
- else {
250
- this.lastBeat = now;
251
- }
252
- if (this.lastViewStoredDuration > 0 && this.lastViewTime > 0) {
253
- this.lastViewTime = now - this.lastViewStoredDuration;
254
- this.lastViewStoredDuration = 0;
255
- }
256
- else {
257
- this.lastViewTime = now;
273
+ try {
274
+ if (!this.trackTime) {
275
+ this.trackTime = true;
276
+ const now = Date.now();
277
+ if (this.lastBeat && this.storedDuration > 0) {
278
+ this.lastBeat = now - this.storedDuration;
279
+ this.storedDuration = 0;
280
+ }
281
+ else {
282
+ this.lastBeat = now;
283
+ }
284
+ if (this.lastViewStoredDuration > 0 && this.lastViewTime > 0) {
285
+ this.lastViewTime = now - this.lastViewStoredDuration;
286
+ this.lastViewStoredDuration = 0;
287
+ }
288
+ else {
289
+ this.lastViewTime = now;
290
+ }
291
+ if (this.debug)
292
+ console.log('[BytemTracker] Time tracking started');
293
+ this.extendSession();
258
294
  }
295
+ }
296
+ catch (e) {
259
297
  if (this.debug)
260
- console.log('[BytemTracker] Time tracking started');
261
- this.extendSession();
298
+ console.error('[BytemTracker] Error in startTime:', e);
262
299
  }
263
300
  }
264
301
  /**
265
302
  * Pauses time tracking for session duration.
266
303
  */
267
304
  stopTime() {
268
- if (this.trackTime) {
269
- this.trackTime = false;
270
- const now = Date.now();
271
- if (this.lastBeat) {
272
- this.storedDuration = now - this.lastBeat;
273
- }
274
- if (this.lastViewTime > 0) {
275
- this.lastViewStoredDuration = now - this.lastViewTime;
305
+ try {
306
+ if (this.trackTime) {
307
+ this.trackTime = false;
308
+ const now = Date.now();
309
+ if (this.lastBeat) {
310
+ this.storedDuration = now - this.lastBeat;
311
+ }
312
+ if (this.lastViewTime > 0) {
313
+ this.lastViewStoredDuration = now - this.lastViewTime;
314
+ }
315
+ if (this.debug)
316
+ console.log('[BytemTracker] Time tracking stopped');
276
317
  }
318
+ }
319
+ catch (e) {
277
320
  if (this.debug)
278
- console.log('[BytemTracker] Time tracking stopped');
321
+ console.error('[BytemTracker] Error in stopTime:', e);
279
322
  }
280
323
  }
281
324
  /**
@@ -288,24 +331,31 @@ class BytemTracker {
288
331
  * @param duration - Optional duration associated with the event.
289
332
  */
290
333
  async trackEvent(eventKey, segmentation, count = 1, sum, duration) {
291
- this.ensureInitialized();
292
- const deviceId = await this.getRealDeviceId();
293
- const now = new Date();
294
- const event = {
295
- key: eventKey,
296
- count: count,
297
- segmentation: segmentation,
298
- timestamp: now.getTime(),
299
- hour: now.getHours(),
300
- dow: now.getDay() === 0 ? 0 : now.getDay(), // JS getDay: 0=Sun, Dart: 7=Sun but mapped to 0. 0-6.
301
- };
302
- if (sum !== undefined)
303
- event.sum = sum;
304
- if (duration !== undefined)
305
- event.dur = duration;
306
- const body = await this.buildBaseRequestParams(deviceId);
307
- body['events'] = JSON.stringify([event]);
308
- await this.sendRequest(this.apiPath, body, 'Event');
334
+ try {
335
+ if (!this.checkInitialized())
336
+ return;
337
+ const deviceId = await this.getRealDeviceId();
338
+ const now = new Date();
339
+ const event = {
340
+ key: eventKey,
341
+ count: count,
342
+ segmentation: segmentation,
343
+ timestamp: now.getTime(),
344
+ hour: now.getHours(),
345
+ dow: now.getDay() === 0 ? 0 : now.getDay(), // JS getDay: 0=Sun, Dart: 7=Sun but mapped to 0. 0-6.
346
+ };
347
+ if (sum !== undefined)
348
+ event.sum = sum;
349
+ if (duration !== undefined)
350
+ event.dur = duration;
351
+ const body = await this.buildBaseRequestParams(deviceId);
352
+ body['events'] = JSON.stringify([event]);
353
+ await this.sendRequest(this.apiPath, body, 'Event');
354
+ }
355
+ catch (e) {
356
+ if (this.debug)
357
+ console.error('[BytemTracker] Error in trackEvent:', e);
358
+ }
309
359
  }
310
360
  /**
311
361
  * Tracks a page view.
@@ -315,26 +365,32 @@ class BytemTracker {
315
365
  * @param referrer - Optional name or path of the previous page.
316
366
  */
317
367
  async trackPageview(current, referrer) {
318
- await this.reportViewDuration();
319
- const segmentation = {
320
- name: current,
321
- current: current,
322
- referrer: '',
323
- };
324
- if (referrer)
325
- segmentation.referrer = referrer;
326
- let duration;
327
- if (this.lastViewTime > 0) {
328
- const now = Date.now();
329
- duration = this.trackTime
330
- ? (now - this.lastViewTime) / 1000
331
- : this.lastViewStoredDuration / 1000;
332
- }
333
- this.lastViewTime = Date.now();
334
- if (!this.trackTime) {
335
- this.lastViewStoredDuration = 0;
336
- }
337
- await this.trackEvent(types_1.BytemEventKeys.view, segmentation, 1, undefined, duration);
368
+ try {
369
+ await this.reportViewDuration();
370
+ const segmentation = {
371
+ name: current,
372
+ current: current,
373
+ referrer: '',
374
+ };
375
+ if (referrer)
376
+ segmentation.referrer = referrer;
377
+ let duration;
378
+ if (this.lastViewTime > 0) {
379
+ const now = Date.now();
380
+ duration = this.trackTime
381
+ ? (now - this.lastViewTime) / 1000
382
+ : this.lastViewStoredDuration / 1000;
383
+ }
384
+ this.lastViewTime = Date.now();
385
+ if (!this.trackTime) {
386
+ this.lastViewStoredDuration = 0;
387
+ }
388
+ await this.trackEvent(types_1.BytemEventKeys.view, segmentation, 1, undefined, duration);
389
+ }
390
+ catch (e) {
391
+ if (this.debug)
392
+ console.error('[BytemTracker] Error in trackPageview:', e);
393
+ }
338
394
  }
339
395
  /**
340
396
  * Tracks a click on a product/goods item.
@@ -342,22 +398,29 @@ class BytemTracker {
342
398
  * @param params - The parameters for the goods click event.
343
399
  */
344
400
  async trackGoodsClick(params) {
345
- this.ensureInitialized();
346
- const segmentation = {
347
- gid: params.gid || '',
348
- skuid: params.skuid || '',
349
- url: params.url,
350
- current: params.current || '',
351
- referrer: params.referrer || '',
352
- visitor_id: this.visitorId || '',
353
- };
354
- if (this.appScheme)
355
- segmentation.domain = this.appScheme;
356
- if (params.source)
357
- segmentation.source = params.source;
358
- if (params.position)
359
- segmentation.position = params.position;
360
- await this.trackEvent('goods_click', segmentation); // Using string literal as Dart does in implementation
401
+ try {
402
+ if (!this.checkInitialized())
403
+ return;
404
+ const segmentation = {
405
+ gid: params.gid || '',
406
+ skuid: params.skuid || '',
407
+ url: params.url,
408
+ current: params.current || '',
409
+ referrer: params.referrer || '',
410
+ visitor_id: this.visitorId || '',
411
+ };
412
+ if (this.appScheme)
413
+ segmentation.domain = this.appScheme;
414
+ if (params.source)
415
+ segmentation.source = params.source;
416
+ if (params.position)
417
+ segmentation.position = params.position;
418
+ await this.trackEvent('goods_click', segmentation); // Using string literal as Dart does in implementation
419
+ }
420
+ catch (e) {
421
+ if (this.debug)
422
+ console.error('[BytemTracker] Error in trackGoodsClick:', e);
423
+ }
361
424
  }
362
425
  // --- Helpers ---
363
426
  async reportViewDuration() {
@@ -492,16 +555,24 @@ class BytemTracker {
492
555
  * @param product - The product object to track.
493
556
  */
494
557
  async trackViewProduct(product) {
495
- // Map Product to segmentation
496
- const segmentation = {
497
- product_id: product.productId,
498
- name: product.name,
499
- price: product.price,
500
- currency: product.currency,
501
- category: product.category,
502
- };
503
- // In Dart, trackViewProduct sends "view_product" event.
504
- await this.trackEvent('view_product', segmentation);
558
+ try {
559
+ if (!this.checkInitialized())
560
+ return;
561
+ // Map Product to segmentation
562
+ const segmentation = {
563
+ product_id: product.productId,
564
+ name: product.name,
565
+ price: product.price,
566
+ currency: product.currency,
567
+ category: product.category,
568
+ };
569
+ // In Dart, trackViewProduct sends "view_product" event.
570
+ await this.trackEvent('view_product', segmentation);
571
+ }
572
+ catch (e) {
573
+ if (this.debug)
574
+ console.error('[BytemTracker] Error in trackViewProduct:', e);
575
+ }
505
576
  }
506
577
  /**
507
578
  * Tracks a checkout order event.
@@ -511,20 +582,28 @@ class BytemTracker {
511
582
  * @param order - The order object containing products to checkout.
512
583
  */
513
584
  async trackCheckOutOrder(order) {
514
- // Map Order to segmentation
515
- const segmentation = {
516
- order_id: order.orderId,
517
- total: order.total,
518
- currency: order.currency,
519
- };
520
- if (order.products && order.products.length > 0) {
521
- segmentation.gids = order.products.map(p => p.productId);
522
- segmentation.prices = order.products.map(p => p.price);
523
- segmentation.quantity = order.products.map(p => p.quantity || 1);
524
- // Optional: skuids if available in Product type, currently not in interface but good to handle if extended
525
- // segmentation.skuids = ...
526
- }
527
- await this.trackEvent('check_out_order', segmentation);
585
+ try {
586
+ if (!this.checkInitialized())
587
+ return;
588
+ // Map Order to segmentation
589
+ const segmentation = {
590
+ order_id: order.orderId,
591
+ total: order.total,
592
+ currency: order.currency,
593
+ };
594
+ if (order.products && order.products.length > 0) {
595
+ segmentation.gids = order.products.map(p => p.productId);
596
+ segmentation.prices = order.products.map(p => p.price);
597
+ segmentation.quantity = order.products.map(p => p.quantity || 1);
598
+ // Optional: skuids if available in Product type, currently not in interface but good to handle if extended
599
+ // segmentation.skuids = ...
600
+ }
601
+ await this.trackEvent('check_out_order', segmentation);
602
+ }
603
+ catch (e) {
604
+ if (this.debug)
605
+ console.error('[BytemTracker] Error in trackCheckOutOrder:', e);
606
+ }
528
607
  }
529
608
  /**
530
609
  * Tracks a payment order event.
@@ -533,21 +612,29 @@ class BytemTracker {
533
612
  * @param order - The order object that was paid for.
534
613
  */
535
614
  async trackPayOrder(order) {
536
- const segmentation = {
537
- order_id: order.orderId,
538
- total: order.total,
539
- currency: order.currency,
540
- };
541
- if (order.products && order.products.length > 0) {
542
- // Dart maps this to a list of maps called "goods"
543
- segmentation.goods = order.products.map(p => ({
544
- gid: p.productId,
545
- name: p.name,
546
- price: p.price,
547
- quantity: p.quantity || 1,
548
- }));
549
- }
550
- await this.trackEvent('pay_order', segmentation);
615
+ try {
616
+ if (!this.checkInitialized())
617
+ return;
618
+ const segmentation = {
619
+ order_id: order.orderId,
620
+ total: order.total,
621
+ currency: order.currency,
622
+ };
623
+ if (order.products && order.products.length > 0) {
624
+ // Dart maps this to a list of maps called "goods"
625
+ segmentation.goods = order.products.map(p => ({
626
+ gid: p.productId,
627
+ name: p.name,
628
+ price: p.price,
629
+ quantity: p.quantity || 1,
630
+ }));
631
+ }
632
+ await this.trackEvent('pay_order', segmentation);
633
+ }
634
+ catch (e) {
635
+ if (this.debug)
636
+ console.error('[BytemTracker] Error in trackPayOrder:', e);
637
+ }
551
638
  }
552
639
  /**
553
640
  * Tracks user details/profile updates.
@@ -556,71 +643,112 @@ class BytemTracker {
556
643
  * @param traits - Additional user properties (email, name, custom fields).
557
644
  */
558
645
  async trackUser(userId, traits) {
559
- // Map to user_details
560
- // This is a special request in Dart: "user_details" param
561
- await this.sendUserDetails(userId, traits);
646
+ try {
647
+ // Map to user_details
648
+ // This is a special request in Dart: "user_details" param
649
+ await this.sendUserDetails(userId, traits);
650
+ }
651
+ catch (e) {
652
+ if (this.debug)
653
+ console.error('[BytemTracker] Error in trackUser:', e);
654
+ }
562
655
  }
563
656
  async sendUserDetails(userId, traits) {
564
- this.ensureInitialized();
565
- const deviceId = await this.getRealDeviceId();
566
- const userData = Object.assign({}, traits);
567
- if (userId) {
568
- userData.custom = Object.assign(Object.assign({}, userData.custom), { user_id: userId, visitor_id: this.visitorId });
569
- }
570
- const body = await this.buildBaseRequestParams(deviceId);
571
- body['user_details'] = JSON.stringify(userData);
572
- await this.sendRequest(this.apiPath, body, 'User Details');
657
+ try {
658
+ if (!this.checkInitialized())
659
+ return;
660
+ const deviceId = await this.getRealDeviceId();
661
+ const userData = Object.assign({}, traits);
662
+ if (userId) {
663
+ userData.custom = Object.assign(Object.assign({}, userData.custom), { user_id: userId, visitor_id: this.visitorId });
664
+ }
665
+ const body = await this.buildBaseRequestParams(deviceId);
666
+ body['user_details'] = JSON.stringify(userData);
667
+ await this.sendRequest(this.apiPath, body, 'User Details');
668
+ }
669
+ catch (e) {
670
+ if (this.debug)
671
+ console.error('[BytemTracker] Error in sendUserDetails:', e);
672
+ }
573
673
  }
574
674
  /**
575
675
  * Clears the current user info and resets visitor ID.
576
676
  */
577
677
  async clearUser() {
578
- this.ensureInitialized();
579
- // Clear visitor ID from storage
580
- await (0, storage_1.removeItem)(storage_1.StorageKeys.VISITOR_ID);
581
- // Generate new visitor ID
582
- this.visitorId = (0, device_1.generateUUID)();
583
- await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
584
- if (this.debug)
585
- console.log(`[BytemTracker] User cleared, new visitor ID: ${this.visitorId}`);
678
+ try {
679
+ if (!this.checkInitialized())
680
+ return;
681
+ // Clear visitor ID from storage
682
+ await (0, storage_1.removeItem)(storage_1.StorageKeys.VISITOR_ID);
683
+ // Generate new visitor ID
684
+ this.visitorId = (0, device_1.generateUUID)();
685
+ await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
686
+ if (this.debug)
687
+ console.log(`[BytemTracker] User cleared, new visitor ID: ${this.visitorId}`);
688
+ }
689
+ catch (e) {
690
+ if (this.debug)
691
+ console.error('[BytemTracker] Error in clearUser:', e);
692
+ }
586
693
  }
587
694
  /**
588
695
  * Sets a custom API path for subsequent requests.
589
696
  * @param path - The new API path (e.g., '/api/track').
590
697
  */
591
698
  setPath(path) {
592
- this.apiPath = path.startsWith('/') ? path : '/' + path;
593
- if (this.debug)
594
- console.log(`[BytemTracker] API path set to: ${this.apiPath}`);
699
+ try {
700
+ this.apiPath = path.startsWith('/') ? path : '/' + path;
701
+ if (this.debug)
702
+ console.log(`[BytemTracker] API path set to: ${this.apiPath}`);
703
+ }
704
+ catch (e) {
705
+ if (this.debug)
706
+ console.error('[BytemTracker] Error in setPath:', e);
707
+ }
595
708
  }
596
709
  /**
597
710
  * Resets the API path to the default value.
598
711
  */
599
712
  resetPath() {
600
- this.apiPath = this.DEFAULT_API_PATH;
601
- if (this.debug)
602
- console.log(`[BytemTracker] API path reset to default: ${this.apiPath}`);
713
+ try {
714
+ this.apiPath = this.DEFAULT_API_PATH;
715
+ if (this.debug)
716
+ console.log(`[BytemTracker] API path reset to default: ${this.apiPath}`);
717
+ }
718
+ catch (e) {
719
+ if (this.debug)
720
+ console.error('[BytemTracker] Error in resetPath:', e);
721
+ }
603
722
  }
604
723
  /**
605
724
  * Retrieves system/device information.
606
725
  */
607
726
  async getSystemInfo() {
608
- const info = await (0, device_1.getDeviceInfo)();
609
- return {
610
- platform: info.platform,
611
- os: info.os,
612
- os_version: info.osVersion,
613
- model: info.model,
614
- resolution: `${Math.round(info.screenWidth)}x${Math.round(info.screenHeight)}`,
615
- language: info.language,
616
- user_agent: info.userAgent,
617
- sdk_version: this.SDK_VERSION,
618
- app_key: this.appKey,
619
- visitor_id: this.visitorId,
620
- device_id_type: this.deviceIdType,
621
- api_path: this.apiPath,
622
- base_url: this.baseUrl,
623
- };
727
+ try {
728
+ const info = await (0, device_1.getDeviceInfo)();
729
+ const deviceId = await this.getRealDeviceId();
730
+ return {
731
+ platform: info.platform,
732
+ os: info.os,
733
+ os_version: info.osVersion,
734
+ model: info.model,
735
+ resolution: `${Math.round(info.screenWidth)}x${Math.round(info.screenHeight)}`,
736
+ language: info.language,
737
+ user_agent: info.userAgent,
738
+ sdk_version: this.SDK_VERSION,
739
+ app_key: this.appKey,
740
+ visitor_id: this.visitorId,
741
+ device_id: deviceId,
742
+ device_id_type: this.deviceIdType,
743
+ api_path: this.apiPath,
744
+ base_url: this.baseUrl,
745
+ };
746
+ }
747
+ catch (e) {
748
+ if (this.debug)
749
+ console.error('[BytemTracker] Error in getSystemInfo:', e);
750
+ return {};
751
+ }
624
752
  }
625
753
  }
626
754
  exports.default = BytemTracker.getInstance();
@@ -4,36 +4,10 @@ exports.generateUUID = exports.getDeviceInfo = void 0;
4
4
  const react_native_1 = require("react-native");
5
5
  const getDeviceInfo = async () => {
6
6
  var _a, _b, _c;
7
- let os = react_native_1.Platform.OS;
8
- let osVersion = String(react_native_1.Platform.Version);
7
+ const os = react_native_1.Platform.OS;
8
+ const osVersion = String(react_native_1.Platform.Version);
9
9
  let model = 'unknown';
10
10
  let userAgent = `BytemTracker/${react_native_1.Platform.OS}`;
11
- try {
12
- // Safely attempt to get native device info
13
- // Use require() dynamically to avoid top-level import crashes in environments like Expo Go
14
- // where react-native-device-info native module is not linked.
15
- const mod = require('react-native-device-info');
16
- const RNDeviceInfo = mod.default || mod;
17
- if (RNDeviceInfo) {
18
- if (typeof RNDeviceInfo.getSystemName === 'function') {
19
- os = RNDeviceInfo.getSystemName();
20
- }
21
- if (typeof RNDeviceInfo.getSystemVersion === 'function') {
22
- osVersion = RNDeviceInfo.getSystemVersion();
23
- }
24
- if (typeof RNDeviceInfo.getModel === 'function') {
25
- model = RNDeviceInfo.getModel();
26
- }
27
- if (typeof RNDeviceInfo.getUserAgent === 'function') {
28
- userAgent = await RNDeviceInfo.getUserAgent();
29
- }
30
- }
31
- }
32
- catch (e) {
33
- // Native module missing or failed, keep default values
34
- // console.warn('[BytemTracker] Native device info not available (lazy load failed).');
35
- }
36
- // Try to get language
37
11
  let language = 'en';
38
12
  try {
39
13
  if (react_native_1.Platform.OS === 'ios') {
@@ -44,9 +18,7 @@ const getDeviceInfo = async () => {
44
18
  language = ((_c = react_native_1.NativeModules.I18nManager) === null || _c === void 0 ? void 0 : _c.localeIdentifier) || 'en';
45
19
  }
46
20
  }
47
- catch (e) {
48
- // Ignore language detection errors
49
- }
21
+ catch (e) { }
50
22
  const { width, height } = react_native_1.Dimensions.get('window');
51
23
  return {
52
24
  platform: react_native_1.Platform.OS,
@@ -61,7 +33,6 @@ const getDeviceInfo = async () => {
61
33
  };
62
34
  exports.getDeviceInfo = getDeviceInfo;
63
35
  const generateUUID = () => {
64
- // Simple UUID v4 generator that doesn't require crypto polyfills
65
36
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
66
37
  const r = (Math.random() * 16) | 0;
67
38
  const v = c === 'x' ? r : (r & 0x3) | 0x8;
@@ -178,6 +178,11 @@ describe('BytemTracker SDK', () => {
178
178
  }
179
179
  });
180
180
  });
181
+ it('should clear user', async () => {
182
+ await BytemTracker_1.default.clearUser();
183
+ expect(async_storage_1.default.removeItem).toHaveBeenCalledWith(storage_1.StorageKeys.VISITOR_ID);
184
+ expect(async_storage_1.default.setItem).toHaveBeenCalledWith(storage_1.StorageKeys.VISITOR_ID, expect.any(String));
185
+ });
181
186
  });
182
187
  describe('Session Management', () => {
183
188
  beforeEach(async () => {
@@ -229,6 +234,44 @@ describe('BytemTracker SDK', () => {
229
234
  quantity: [1, 1]
230
235
  });
231
236
  });
237
+ it('should track view product', async () => {
238
+ const product = {
239
+ productId: 'prod_123',
240
+ name: 'Test Product',
241
+ price: 99.99,
242
+ currency: 'USD',
243
+ category: 'Electronics'
244
+ };
245
+ await BytemTracker_1.default.trackViewProduct(product);
246
+ const calls = global.fetch.mock.calls;
247
+ const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
248
+ const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
249
+ expect(events[0].key).toBe('view_product');
250
+ expect(events[0].segmentation).toMatchObject({
251
+ product_id: 'prod_123',
252
+ name: 'Test Product',
253
+ price: 99.99,
254
+ currency: 'USD',
255
+ category: 'Electronics'
256
+ });
257
+ });
258
+ it('should track goods click', async () => {
259
+ const params = {
260
+ gid: 'goods_123',
261
+ url: 'https://example.com/product/123',
262
+ source: 'recommendation'
263
+ };
264
+ await BytemTracker_1.default.trackGoodsClick(params);
265
+ const calls = global.fetch.mock.calls;
266
+ const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
267
+ const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
268
+ expect(events[0].key).toBe('goods_click');
269
+ expect(events[0].segmentation).toMatchObject({
270
+ gid: 'goods_123',
271
+ url: 'https://example.com/product/123',
272
+ source: 'recommendation'
273
+ });
274
+ });
232
275
  it('should track pay order', async () => {
233
276
  const order = {
234
277
  orderId: 'order_123',
@@ -255,4 +298,15 @@ describe('BytemTracker SDK', () => {
255
298
  });
256
299
  });
257
300
  });
301
+ describe('System Info', () => {
302
+ beforeEach(async () => {
303
+ await BytemTracker_1.default.init(mockConfig);
304
+ });
305
+ it('should return system info with device_id', async () => {
306
+ const info = await BytemTracker_1.default.getSystemInfo();
307
+ expect(info.device_id).toBeDefined();
308
+ expect(typeof info.device_id).toBe('string');
309
+ expect(info.app_key).toBe(mockConfig.appId);
310
+ });
311
+ });
258
312
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const BytemTracker_1 = __importDefault(require("../src/BytemTracker"));
7
+ const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
8
+ describe('BytemTracker Network Error Resilience', () => {
9
+ const mockConfig = {
10
+ appId: 'test-app-id',
11
+ endpoint: 'https://api.example.com/track',
12
+ debug: true,
13
+ };
14
+ beforeEach(() => {
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;
29
+ // @ts-ignore
30
+ BytemTracker_1.default.trackTime = true;
31
+ // @ts-ignore
32
+ BytemTracker_1.default.storedDuration = 0;
33
+ // @ts-ignore
34
+ BytemTracker_1.default.lastViewTime = 0;
35
+ // @ts-ignore
36
+ BytemTracker_1.default.lastViewStoredDuration = 0;
37
+ // Clear storage mocks
38
+ // @ts-ignore
39
+ async_storage_1.default.clear();
40
+ });
41
+ it('should catch Network request failed error and not crash', async () => {
42
+ // Mock fetch to reject with the specific error user mentioned
43
+ global.fetch = jest.fn(() => Promise.reject(new TypeError('Network request failed')));
44
+ // Initialize SDK
45
+ await BytemTracker_1.default.init(mockConfig);
46
+ // 1. Test trackEvent
47
+ console.log('Testing trackEvent with network error...');
48
+ await expect(BytemTracker_1.default.trackEvent('test_event', { foo: 'bar' })).resolves.not.toThrow();
49
+ // 2. Test beginSession
50
+ console.log('Testing beginSession with network error...');
51
+ await expect(BytemTracker_1.default.beginSession(true)).resolves.not.toThrow();
52
+ // 3. Test endSession
53
+ console.log('Testing endSession with network error...');
54
+ await expect(BytemTracker_1.default.endSession(undefined, true)).resolves.not.toThrow();
55
+ // Verify fetch was actually called (so we know it failed)
56
+ expect(global.fetch).toHaveBeenCalled();
57
+ console.log('Network resilience test passed ✅');
58
+ });
59
+ });
@@ -25,7 +25,7 @@ jest.mock('react-native-device-info', () => ({
25
25
  getSystemVersion: jest.fn(() => '14.0'),
26
26
  getModel: jest.fn(() => 'iPhone 11'),
27
27
  getUserAgent: jest.fn(() => Promise.resolve('Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)')),
28
- }));
28
+ }), { virtual: true });
29
29
  // Mock React Native
30
30
  jest.mock('react-native', () => ({
31
31
  Platform: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytem/bytem-tracker-app",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Bytem Tracker SDK for React Native",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -30,8 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "uuid": "^9.0.0",
33
- "@react-native-async-storage/async-storage": "^1.19.0",
34
- "react-native-device-info": "^10.8.0"
33
+ "@react-native-async-storage/async-storage": "^1.19.0"
35
34
  },
36
35
  "devDependencies": {
37
36
  "@react-native-async-storage/async-storage": "^1.19.0",
@@ -43,7 +42,6 @@
43
42
  "jest": "^30.2.0",
44
43
  "react": "18.2.0",
45
44
  "react-native": "0.72.0",
46
- "react-native-device-info": "^10.8.0",
47
45
  "ts-jest": "^29.4.6",
48
46
  "typescript": "^5.0.0"
49
47
  },