@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 +2 -4
- package/dist/src/BytemTracker.d.ts +1 -1
- package/dist/src/BytemTracker.js +394 -266
- package/dist/src/core/device.js +3 -32
- package/dist/test/BytemTracker.test.js +54 -0
- package/dist/test/network_error.test.d.ts +1 -0
- package/dist/test/network_error.test.js +59 -0
- package/dist/test/setup.js +1 -1
- package/package.json +2 -4
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytem/bytem-tracker-app",
|
|
3
|
-
"version": "0.0.
|
|
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
|
|
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.
|
package/dist/src/BytemTracker.js
CHANGED
|
@@ -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 = '
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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]
|
|
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]
|
|
91
|
+
console.log(`[BytemTracker] Using developer-set device ID: ${config.deviceId}`);
|
|
81
92
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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.
|
|
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
|
-
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
if (this.debug)
|
|
118
|
+
console.error('[BytemTracker] Init failed:', e);
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
|
-
|
|
121
|
+
checkInitialized() {
|
|
116
122
|
if (!this.isInitialized) {
|
|
117
|
-
|
|
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.
|
|
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
|
-
|
|
174
|
-
|
|
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.
|
|
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
|
-
|
|
194
|
-
|
|
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(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
243
|
-
this.trackTime
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.lastBeat
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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.
|
|
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
|
-
|
|
269
|
-
this.trackTime
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
this.
|
|
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.
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
this.
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
userData
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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();
|
package/dist/src/core/device.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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
|
+
});
|
package/dist/test/setup.js
CHANGED
|
@@ -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.
|
|
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
|
},
|