@bytem/bytem-tracker-app 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -48
- package/dist/src/BytemTracker.d.ts +129 -8
- package/dist/src/BytemTracker.js +526 -87
- package/dist/src/types.d.ts +36 -0
- package/dist/src/types.js +13 -0
- package/dist/test/BytemTracker.test.js +231 -57
- package/dist/test/debug.test.js +37 -5
- package/dist/test/setup.js +1 -0
- package/package.json +1 -1
package/dist/src/BytemTracker.js
CHANGED
|
@@ -1,50 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
const
|
|
37
|
-
const request_1 = require("./core/request");
|
|
38
|
-
const Business = __importStar(require("./business"));
|
|
39
|
-
const session_1 = require("./core/session");
|
|
3
|
+
const types_1 = require("./types");
|
|
40
4
|
const storage_1 = require("./core/storage");
|
|
41
5
|
const device_1 = require("./core/device");
|
|
42
6
|
class BytemTracker {
|
|
43
7
|
constructor() {
|
|
44
|
-
|
|
45
|
-
this.
|
|
8
|
+
// SDK Constants
|
|
9
|
+
this.SDK_NAME = 'react_native_bytem';
|
|
10
|
+
this.SDK_VERSION = '0.0.1'; // Should match package.json
|
|
11
|
+
this.DEFAULT_ENDPOINT = 'https://tracking.server.bytecon.com';
|
|
12
|
+
this.DEFAULT_API_PATH = '/i';
|
|
13
|
+
// Config
|
|
14
|
+
this.appKey = null;
|
|
15
|
+
this.baseUrl = this.DEFAULT_ENDPOINT;
|
|
16
|
+
this.debug = false;
|
|
46
17
|
this.visitorId = null;
|
|
47
|
-
this.
|
|
18
|
+
this.deviceIdType = 1; // 1: Auto-generated
|
|
19
|
+
this.appScheme = null;
|
|
20
|
+
this.apiPath = this.DEFAULT_API_PATH;
|
|
21
|
+
this.isInitialized = false;
|
|
22
|
+
// Session Management
|
|
23
|
+
this.sessionStarted = false;
|
|
24
|
+
this.useSessionCookie = true;
|
|
25
|
+
this.sessionCookieTimeout = 30; // minutes
|
|
26
|
+
this.lastBeat = null;
|
|
27
|
+
this.trackTime = true;
|
|
28
|
+
this.storedDuration = 0;
|
|
29
|
+
this.lastViewTime = 0;
|
|
30
|
+
this.lastViewStoredDuration = 0;
|
|
48
31
|
}
|
|
49
32
|
static getInstance() {
|
|
50
33
|
if (!BytemTracker.instance) {
|
|
@@ -52,84 +35,540 @@ class BytemTracker {
|
|
|
52
35
|
}
|
|
53
36
|
return BytemTracker.instance;
|
|
54
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Initializes the BytemTracker SDK with the provided configuration.
|
|
40
|
+
* This method must be called before any other tracking methods.
|
|
41
|
+
*
|
|
42
|
+
* @param config - The configuration object for the tracker.
|
|
43
|
+
* @returns A promise that resolves when initialization is complete.
|
|
44
|
+
*/
|
|
55
45
|
async init(config) {
|
|
56
46
|
if (this.isInitialized) {
|
|
57
47
|
console.warn('[BytemTracker] Already initialized');
|
|
58
48
|
return;
|
|
59
49
|
}
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
this.appKey = config.appId;
|
|
51
|
+
if (config.endpoint) {
|
|
52
|
+
this.baseUrl = config.endpoint;
|
|
53
|
+
}
|
|
54
|
+
this.debug = !!config.debug;
|
|
55
|
+
this.appScheme = config.appScheme || null;
|
|
56
|
+
if (config.path) {
|
|
57
|
+
this.apiPath = config.path.startsWith('/') ? config.path : '/' + config.path;
|
|
58
|
+
}
|
|
59
|
+
// Initialize Visitor ID logic:
|
|
60
|
+
// 1. If provided in config, use it.
|
|
61
|
+
// 2. If not, try to retrieve from storage.
|
|
62
|
+
// 3. If not in storage, generate a new UUID.
|
|
63
63
|
if (config.visitorId) {
|
|
64
64
|
this.visitorId = config.visitorId;
|
|
65
65
|
await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
|
|
66
|
+
if (this.debug)
|
|
67
|
+
console.log(`[BytemTracker] Using provided visitor ID: ${this.visitorId}`);
|
|
66
68
|
}
|
|
67
69
|
else {
|
|
68
70
|
this.visitorId = await (0, storage_1.getItem)(storage_1.StorageKeys.VISITOR_ID);
|
|
69
71
|
if (!this.visitorId) {
|
|
70
72
|
this.visitorId = (0, device_1.generateUUID)();
|
|
71
73
|
await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
|
|
74
|
+
if (this.debug)
|
|
75
|
+
console.log(`[BytemTracker] Generated new visitor ID: ${this.visitorId}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (this.debug)
|
|
79
|
+
console.log(`[BytemTracker] Restored visitor ID: ${this.visitorId}`);
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
|
-
// Initialize
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
// Initialize Device ID type logic:
|
|
83
|
+
// 0: Developer supplied
|
|
84
|
+
// 1: SDK generated (default)
|
|
85
|
+
if (config.deviceId) {
|
|
86
|
+
this.deviceIdType = 0; // Developer set
|
|
87
|
+
await (0, storage_1.setItem)(storage_1.StorageKeys.DEVICE_ID, config.deviceId);
|
|
88
|
+
if (this.debug)
|
|
89
|
+
console.log(`[BytemTracker] Using developer-set device ID: ${config.deviceId}`);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.deviceIdType = 1; // Auto generated (using visitor ID or platform ID logic)
|
|
93
|
+
}
|
|
94
|
+
this.isInitialized = true;
|
|
95
|
+
if (this.debug) {
|
|
96
|
+
console.log('[BytemTracker] Initialized ✅');
|
|
97
|
+
console.log(` app_key: ${this.appKey}`);
|
|
98
|
+
console.log(` base_url: ${this.baseUrl}`);
|
|
99
|
+
console.log(` visitor_id: ${this.visitorId}`);
|
|
100
|
+
if (this.appScheme)
|
|
101
|
+
console.log(` app_scheme: ${this.appScheme}`);
|
|
102
|
+
}
|
|
103
|
+
// Attempt to begin a session automatically on init
|
|
104
|
+
try {
|
|
105
|
+
await this.beginSession();
|
|
78
106
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
107
|
+
catch (e) {
|
|
108
|
+
if (this.debug) {
|
|
109
|
+
console.warn('[BytemTracker] Failed to begin session during init:', e);
|
|
110
|
+
}
|
|
111
|
+
this.sessionStarted = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
ensureInitialized() {
|
|
115
|
+
if (!this.isInitialized) {
|
|
116
|
+
throw new Error('[BytemTracker] Not initialized. Call await init() first.');
|
|
82
117
|
}
|
|
83
118
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Begins a new user session.
|
|
121
|
+
* Handles session cookie logic to prevent excessive session starts if one is already active.
|
|
122
|
+
*
|
|
123
|
+
* @param force - If true, forces a new session start request even if the session cookie is valid.
|
|
124
|
+
*/
|
|
125
|
+
async beginSession(force = false) {
|
|
126
|
+
this.ensureInitialized();
|
|
127
|
+
if (this.sessionStarted) {
|
|
128
|
+
if (this.debug)
|
|
129
|
+
console.log('[BytemTracker] Session already started, skipping');
|
|
87
130
|
return;
|
|
88
131
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
132
|
+
try {
|
|
133
|
+
this.sessionStarted = true;
|
|
134
|
+
this.lastBeat = Date.now();
|
|
135
|
+
// Check session cookie to avoid duplicate session starts
|
|
136
|
+
const sessionKey = `${this.appKey}/cly_session`;
|
|
137
|
+
const expireTimestampStr = await (0, storage_1.getItem)(sessionKey);
|
|
138
|
+
const expireTimestamp = expireTimestampStr ? parseInt(expireTimestampStr, 10) : null;
|
|
139
|
+
const currentTimestamp = Date.now();
|
|
140
|
+
const shouldSendRequest = force ||
|
|
141
|
+
!this.useSessionCookie ||
|
|
142
|
+
expireTimestamp === null ||
|
|
143
|
+
expireTimestamp <= currentTimestamp;
|
|
144
|
+
if (!shouldSendRequest) {
|
|
145
|
+
if (this.debug)
|
|
146
|
+
console.log('[BytemTracker] Session cookie still valid, skipping begin_session request');
|
|
147
|
+
// Extend the cookie validity
|
|
148
|
+
await (0, storage_1.setItem)(sessionKey, (currentTimestamp + this.sessionCookieTimeout * 60 * 1000).toString());
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const deviceId = await this.getRealDeviceId();
|
|
152
|
+
const metrics = await this.getMetrics();
|
|
153
|
+
const body = await this.buildBaseRequestParams(deviceId);
|
|
154
|
+
body['begin_session'] = '1';
|
|
155
|
+
body['metrics'] = JSON.stringify(metrics);
|
|
156
|
+
await this.sendRequest(this.apiPath, body, 'Begin Session');
|
|
157
|
+
await (0, storage_1.setItem)(sessionKey, (currentTimestamp + this.sessionCookieTimeout * 60 * 1000).toString());
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
this.sessionStarted = false;
|
|
161
|
+
if (this.debug)
|
|
162
|
+
console.error('[BytemTracker] Error in beginSession:', e);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Reports session duration to the server.
|
|
167
|
+
* Usually called internally or when app state changes.
|
|
168
|
+
*
|
|
169
|
+
* @param sec - The duration in seconds to report.
|
|
170
|
+
*/
|
|
171
|
+
async sessionDuration(sec) {
|
|
172
|
+
this.ensureInitialized();
|
|
173
|
+
if (!this.sessionStarted) {
|
|
174
|
+
if (this.debug)
|
|
175
|
+
console.log('[BytemTracker] Session not started, skipping session_duration');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const deviceId = await this.getRealDeviceId();
|
|
179
|
+
const body = await this.buildBaseRequestParams(deviceId);
|
|
180
|
+
body['session_duration'] = sec.toString();
|
|
181
|
+
await this.sendRequest(this.apiPath, body, 'Session Duration');
|
|
182
|
+
this.lastBeat = Date.now();
|
|
183
|
+
await this.extendSession();
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Ends the current user session.
|
|
187
|
+
*
|
|
188
|
+
* @param sec - Optional duration of the session in seconds. If not provided, it's calculated from the last beat.
|
|
189
|
+
* @param force - If true, forces the end session request even if using session cookies.
|
|
190
|
+
*/
|
|
191
|
+
async endSession(sec, force = false) {
|
|
192
|
+
this.ensureInitialized();
|
|
193
|
+
if (!this.sessionStarted) {
|
|
194
|
+
if (this.debug)
|
|
195
|
+
console.log('[BytemTracker] Session not started, skipping end_session');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const currentTimestamp = Date.now();
|
|
199
|
+
const sessionSec = sec !== null && sec !== void 0 ? sec : (this.lastBeat ? (currentTimestamp - this.lastBeat) / 1000 : 0);
|
|
200
|
+
if (this.debug)
|
|
201
|
+
console.log(`[BytemTracker] Ending session with duration: ${sessionSec} seconds`);
|
|
202
|
+
if (this.useSessionCookie && !force) {
|
|
203
|
+
await this.sessionDuration(sessionSec);
|
|
204
|
+
this.sessionStarted = false;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const deviceId = await this.getRealDeviceId();
|
|
208
|
+
const body = await this.buildBaseRequestParams(deviceId);
|
|
209
|
+
body['end_session'] = '1';
|
|
210
|
+
body['session_duration'] = sessionSec.toString();
|
|
211
|
+
await this.sendRequest(this.apiPath, body, 'End Session');
|
|
212
|
+
this.sessionStarted = false;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Manually tracks or extends a session.
|
|
216
|
+
* If a session is active, it reports duration. If not, it begins a new session.
|
|
217
|
+
*/
|
|
218
|
+
async trackSessions() {
|
|
219
|
+
this.ensureInitialized();
|
|
220
|
+
if (this.debug)
|
|
221
|
+
console.log('[BytemTracker] trackSessions called');
|
|
222
|
+
if (this.sessionStarted && this.lastBeat) {
|
|
223
|
+
const duration = (Date.now() - this.lastBeat) / 1000;
|
|
224
|
+
if (duration > 0) {
|
|
225
|
+
if (this.debug)
|
|
226
|
+
console.log(`[BytemTracker] Session already started, sending session_duration: ${duration}s`);
|
|
227
|
+
await this.sessionDuration(duration);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
await this.beginSession();
|
|
232
|
+
}
|
|
233
|
+
this.startTime();
|
|
234
|
+
if (this.debug)
|
|
235
|
+
console.log('[BytemTracker] Session tracking started');
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Resumes time tracking for session duration.
|
|
239
|
+
*/
|
|
240
|
+
startTime() {
|
|
241
|
+
if (!this.trackTime) {
|
|
242
|
+
this.trackTime = true;
|
|
243
|
+
const now = Date.now();
|
|
244
|
+
if (this.lastBeat && this.storedDuration > 0) {
|
|
245
|
+
this.lastBeat = now - this.storedDuration;
|
|
246
|
+
this.storedDuration = 0;
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
this.lastBeat = now;
|
|
250
|
+
}
|
|
251
|
+
if (this.lastViewStoredDuration > 0 && this.lastViewTime > 0) {
|
|
252
|
+
this.lastViewTime = now - this.lastViewStoredDuration;
|
|
253
|
+
this.lastViewStoredDuration = 0;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
this.lastViewTime = now;
|
|
257
|
+
}
|
|
258
|
+
if (this.debug)
|
|
259
|
+
console.log('[BytemTracker] Time tracking started');
|
|
260
|
+
this.extendSession();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Pauses time tracking for session duration.
|
|
265
|
+
*/
|
|
266
|
+
stopTime() {
|
|
267
|
+
if (this.trackTime) {
|
|
268
|
+
this.trackTime = false;
|
|
269
|
+
const now = Date.now();
|
|
270
|
+
if (this.lastBeat) {
|
|
271
|
+
this.storedDuration = now - this.lastBeat;
|
|
272
|
+
}
|
|
273
|
+
if (this.lastViewTime > 0) {
|
|
274
|
+
this.lastViewStoredDuration = now - this.lastViewTime;
|
|
275
|
+
}
|
|
276
|
+
if (this.debug)
|
|
277
|
+
console.log('[BytemTracker] Time tracking stopped');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Tracks a custom event.
|
|
282
|
+
*
|
|
283
|
+
* @param eventKey - The unique key/name for the event.
|
|
284
|
+
* @param segmentation - Optional key-value pairs to attach to the event.
|
|
285
|
+
* @param count - The number of times this event occurred (default: 1).
|
|
286
|
+
* @param sum - Optional numeric value associated with the event (e.g., purchase amount).
|
|
287
|
+
* @param duration - Optional duration associated with the event.
|
|
288
|
+
*/
|
|
289
|
+
async trackEvent(eventKey, segmentation, count = 1, sum, duration) {
|
|
290
|
+
this.ensureInitialized();
|
|
291
|
+
const deviceId = await this.getRealDeviceId();
|
|
292
|
+
const now = new Date();
|
|
293
|
+
const event = {
|
|
294
|
+
key: eventKey,
|
|
295
|
+
count: count,
|
|
296
|
+
segmentation: segmentation,
|
|
297
|
+
timestamp: now.getTime(),
|
|
298
|
+
hour: now.getHours(),
|
|
299
|
+
dow: now.getDay() === 0 ? 0 : now.getDay(), // JS getDay: 0=Sun, Dart: 7=Sun but mapped to 0. 0-6.
|
|
300
|
+
};
|
|
301
|
+
if (sum !== undefined)
|
|
302
|
+
event.sum = sum;
|
|
303
|
+
if (duration !== undefined)
|
|
304
|
+
event.dur = duration;
|
|
305
|
+
const body = await this.buildBaseRequestParams(deviceId);
|
|
306
|
+
body['events'] = JSON.stringify([event]);
|
|
307
|
+
await this.sendRequest(this.apiPath, body, 'Event');
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Tracks a page view.
|
|
311
|
+
* Automatically handles reporting the duration of the previous page view.
|
|
312
|
+
*
|
|
313
|
+
* @param current - The name or path of the current page.
|
|
314
|
+
* @param referrer - Optional name or path of the previous page.
|
|
315
|
+
*/
|
|
316
|
+
async trackPageview(current, referrer) {
|
|
317
|
+
await this.reportViewDuration();
|
|
318
|
+
const segmentation = {
|
|
319
|
+
name: current,
|
|
320
|
+
current: current,
|
|
321
|
+
referrer: '',
|
|
322
|
+
};
|
|
323
|
+
if (referrer)
|
|
324
|
+
segmentation.referrer = referrer;
|
|
325
|
+
let duration;
|
|
326
|
+
if (this.lastViewTime > 0) {
|
|
327
|
+
const now = Date.now();
|
|
328
|
+
duration = this.trackTime
|
|
329
|
+
? (now - this.lastViewTime) / 1000
|
|
330
|
+
: this.lastViewStoredDuration / 1000;
|
|
331
|
+
}
|
|
332
|
+
this.lastViewTime = Date.now();
|
|
333
|
+
if (!this.trackTime) {
|
|
334
|
+
this.lastViewStoredDuration = 0;
|
|
335
|
+
}
|
|
336
|
+
await this.trackEvent(types_1.BytemEventKeys.view, segmentation, 1, undefined, duration);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Tracks a click on a product/goods item.
|
|
340
|
+
*
|
|
341
|
+
* @param params - The parameters for the goods click event.
|
|
342
|
+
*/
|
|
343
|
+
async trackGoodsClick(params) {
|
|
344
|
+
this.ensureInitialized();
|
|
345
|
+
const segmentation = {
|
|
346
|
+
gid: params.gid || '',
|
|
347
|
+
skuid: params.skuid || '',
|
|
348
|
+
url: params.url,
|
|
349
|
+
current: params.current || '',
|
|
350
|
+
referrer: params.referrer || '',
|
|
351
|
+
visitor_id: this.visitorId || '',
|
|
352
|
+
};
|
|
353
|
+
if (this.appScheme)
|
|
354
|
+
segmentation.domain = this.appScheme;
|
|
355
|
+
if (params.source)
|
|
356
|
+
segmentation.source = params.source;
|
|
357
|
+
if (params.position)
|
|
358
|
+
segmentation.position = params.position;
|
|
359
|
+
await this.trackEvent('goods_click', segmentation); // Using string literal as Dart does in implementation
|
|
360
|
+
}
|
|
361
|
+
// --- Helpers ---
|
|
362
|
+
async reportViewDuration() {
|
|
363
|
+
if (this.lastViewTime === 0)
|
|
364
|
+
return;
|
|
365
|
+
const now = Date.now();
|
|
366
|
+
const duration = this.trackTime
|
|
367
|
+
? (now - this.lastViewTime) / 1000
|
|
368
|
+
: this.lastViewStoredDuration / 1000;
|
|
369
|
+
if (duration > 0) {
|
|
370
|
+
if (this.debug)
|
|
371
|
+
console.log(`[BytemTracker] Last page duration: ${duration}s`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async getRealDeviceId() {
|
|
375
|
+
// 1. Check developer set ID
|
|
376
|
+
if (this.deviceIdType === 0) {
|
|
377
|
+
const stored = await (0, storage_1.getItem)(storage_1.StorageKeys.DEVICE_ID);
|
|
378
|
+
if (stored)
|
|
379
|
+
return stored;
|
|
380
|
+
}
|
|
381
|
+
// 2. Use visitor ID
|
|
382
|
+
return this.visitorId || (0, device_1.generateUUID)();
|
|
383
|
+
}
|
|
384
|
+
async getMetrics() {
|
|
385
|
+
const metrics = {
|
|
386
|
+
_sdk_name: this.SDK_NAME,
|
|
387
|
+
_sdk_version: this.SDK_VERSION,
|
|
388
|
+
};
|
|
389
|
+
try {
|
|
390
|
+
const info = await (0, device_1.getDeviceInfo)();
|
|
391
|
+
metrics._platform_version = info.osVersion;
|
|
392
|
+
metrics._app_version = this.SDK_VERSION; // Or get actual app version if available
|
|
393
|
+
metrics._resolution = `${Math.round(info.screenWidth)}x${Math.round(info.screenHeight)}`;
|
|
394
|
+
metrics._locale = info.language;
|
|
395
|
+
// _density could be added if available in getDeviceInfo
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
if (this.debug)
|
|
399
|
+
console.warn('[BytemTracker] Error getting metrics:', e);
|
|
400
|
+
}
|
|
401
|
+
return metrics;
|
|
402
|
+
}
|
|
403
|
+
async buildBaseRequestParams(deviceId) {
|
|
404
|
+
const now = new Date();
|
|
405
|
+
const visitorId = this.visitorId || (0, device_1.generateUUID)();
|
|
406
|
+
const params = {
|
|
407
|
+
app_key: this.appKey,
|
|
408
|
+
device_id: deviceId,
|
|
409
|
+
kid: visitorId,
|
|
410
|
+
visitor_id: visitorId,
|
|
411
|
+
sdk_name: this.SDK_NAME,
|
|
412
|
+
sdk_version: this.SDK_VERSION,
|
|
413
|
+
t: this.deviceIdType.toString(),
|
|
414
|
+
timestamp: now.getTime().toString(),
|
|
415
|
+
hour: now.getHours().toString(),
|
|
416
|
+
dow: (now.getDay() === 0 ? 0 : now.getDay()).toString(),
|
|
417
|
+
};
|
|
418
|
+
// Add location info if available (not implemented yet in this class properties)
|
|
419
|
+
return params;
|
|
420
|
+
}
|
|
421
|
+
async extendSession() {
|
|
422
|
+
if (!this.useSessionCookie)
|
|
423
|
+
return;
|
|
424
|
+
const sessionKey = `${this.appKey}/cly_session`;
|
|
425
|
+
const expireTimestampStr = await (0, storage_1.getItem)(sessionKey);
|
|
426
|
+
const currentTimestamp = Date.now();
|
|
427
|
+
const expireTimestamp = expireTimestampStr ? parseInt(expireTimestampStr, 10) : null;
|
|
428
|
+
if (expireTimestamp === null || expireTimestamp <= currentTimestamp) {
|
|
429
|
+
if (this.debug)
|
|
430
|
+
console.log('[BytemTracker] Session expired, restarting session');
|
|
431
|
+
this.sessionStarted = false;
|
|
432
|
+
await this.beginSession(true);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
await (0, storage_1.setItem)(sessionKey, (currentTimestamp + this.sessionCookieTimeout * 60 * 1000).toString());
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async sendRequest(path, body, requestName) {
|
|
439
|
+
// Manually constructing URL and using fetch with urlencoded body
|
|
440
|
+
let requestUrl = this.baseUrl;
|
|
441
|
+
try {
|
|
442
|
+
// Handle endpoint/path logic
|
|
443
|
+
const urlObj = new URL(this.baseUrl);
|
|
444
|
+
if (path) {
|
|
445
|
+
if (path.startsWith('http')) {
|
|
446
|
+
requestUrl = path;
|
|
107
447
|
}
|
|
108
448
|
else {
|
|
109
|
-
|
|
449
|
+
// If base url has path, append or replace?
|
|
450
|
+
// Dart implementation just concatenates: "$_baseUrl$path"
|
|
451
|
+
// But _baseUrl usually has no trailing slash, and path starts with /
|
|
452
|
+
const base = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
453
|
+
const endpointPath = path.startsWith('/') ? path : '/' + path;
|
|
454
|
+
requestUrl = `${base}${endpointPath}`;
|
|
110
455
|
}
|
|
111
456
|
}
|
|
112
|
-
|
|
113
|
-
|
|
457
|
+
}
|
|
458
|
+
catch (e) {
|
|
459
|
+
requestUrl = this.baseUrl + path;
|
|
460
|
+
}
|
|
461
|
+
if (this.debug) {
|
|
462
|
+
console.log(`📤 [BytemTracker] Sending ${requestName}:`);
|
|
463
|
+
console.log(` URL: ${requestUrl}`);
|
|
464
|
+
console.log(` Body:`, body);
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
// Convert body to urlencoded string
|
|
468
|
+
const formBody = Object.keys(body).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(body[key])).join('&');
|
|
469
|
+
const response = await fetch(requestUrl, {
|
|
470
|
+
method: 'POST',
|
|
471
|
+
headers: {
|
|
472
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
473
|
+
},
|
|
474
|
+
body: formBody,
|
|
475
|
+
});
|
|
476
|
+
if (this.debug) {
|
|
477
|
+
const resText = await response.text();
|
|
478
|
+
console.log(`📥 [BytemTracker] Response (${response.status}): ${resText}`);
|
|
114
479
|
}
|
|
115
|
-
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
if (this.debug)
|
|
483
|
+
console.error(`❌ [BytemTracker] Error sending ${requestName}:`, e);
|
|
116
484
|
}
|
|
117
485
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
486
|
+
// Wrappers for Business methods to maintain compatibility or you can refactor them too
|
|
487
|
+
/**
|
|
488
|
+
* Tracks a product view event.
|
|
489
|
+
* Maps the product details to a 'view_product' event.
|
|
490
|
+
*
|
|
491
|
+
* @param product - The product object to track.
|
|
492
|
+
*/
|
|
493
|
+
async trackViewProduct(product) {
|
|
494
|
+
// Map Product to segmentation
|
|
495
|
+
const segmentation = {
|
|
496
|
+
product_id: product.productId,
|
|
497
|
+
name: product.name,
|
|
498
|
+
price: product.price,
|
|
499
|
+
currency: product.currency,
|
|
500
|
+
category: product.category,
|
|
501
|
+
};
|
|
502
|
+
// In Dart, trackViewProduct sends "view_product" event.
|
|
503
|
+
await this.trackEvent('view_product', segmentation);
|
|
121
504
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
505
|
+
/**
|
|
506
|
+
* Tracks a checkout order event.
|
|
507
|
+
* Automatically splits product lists into parallel arrays (gids, prices, quantities)
|
|
508
|
+
* to match the backend requirement.
|
|
509
|
+
*
|
|
510
|
+
* @param order - The order object containing products to checkout.
|
|
511
|
+
*/
|
|
512
|
+
async trackCheckOutOrder(order) {
|
|
513
|
+
// Map Order to segmentation
|
|
514
|
+
const segmentation = {
|
|
515
|
+
order_id: order.orderId,
|
|
516
|
+
total: order.total,
|
|
517
|
+
currency: order.currency,
|
|
518
|
+
};
|
|
519
|
+
if (order.products && order.products.length > 0) {
|
|
520
|
+
segmentation.gids = order.products.map(p => p.productId);
|
|
521
|
+
segmentation.prices = order.products.map(p => p.price);
|
|
522
|
+
segmentation.quantity = order.products.map(p => p.quantity || 1);
|
|
523
|
+
// Optional: skuids if available in Product type, currently not in interface but good to handle if extended
|
|
524
|
+
// segmentation.skuids = ...
|
|
525
|
+
}
|
|
526
|
+
await this.trackEvent('check_out_order', segmentation);
|
|
125
527
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
528
|
+
/**
|
|
529
|
+
* Tracks a payment order event.
|
|
530
|
+
* Maps product list to a 'goods' array structure expected by the backend.
|
|
531
|
+
*
|
|
532
|
+
* @param order - The order object that was paid for.
|
|
533
|
+
*/
|
|
534
|
+
async trackPayOrder(order) {
|
|
535
|
+
const segmentation = {
|
|
536
|
+
order_id: order.orderId,
|
|
537
|
+
total: order.total,
|
|
538
|
+
currency: order.currency,
|
|
539
|
+
};
|
|
540
|
+
if (order.products && order.products.length > 0) {
|
|
541
|
+
// Dart maps this to a list of maps called "goods"
|
|
542
|
+
segmentation.goods = order.products.map(p => ({
|
|
543
|
+
gid: p.productId,
|
|
544
|
+
name: p.name,
|
|
545
|
+
price: p.price,
|
|
546
|
+
quantity: p.quantity || 1,
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
await this.trackEvent('pay_order', segmentation);
|
|
129
550
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
551
|
+
/**
|
|
552
|
+
* Tracks user details/profile updates.
|
|
553
|
+
*
|
|
554
|
+
* @param userId - The unique identifier for the user.
|
|
555
|
+
* @param traits - Additional user properties (email, name, custom fields).
|
|
556
|
+
*/
|
|
557
|
+
async trackUser(userId, traits) {
|
|
558
|
+
// Map to user_details
|
|
559
|
+
// This is a special request in Dart: "user_details" param
|
|
560
|
+
await this.sendUserDetails(userId, traits);
|
|
561
|
+
}
|
|
562
|
+
async sendUserDetails(userId, traits) {
|
|
563
|
+
this.ensureInitialized();
|
|
564
|
+
const deviceId = await this.getRealDeviceId();
|
|
565
|
+
const userData = Object.assign({}, traits);
|
|
566
|
+
if (userId) {
|
|
567
|
+
userData.custom = Object.assign(Object.assign({}, userData.custom), { user_id: userId, visitor_id: this.visitorId });
|
|
568
|
+
}
|
|
569
|
+
const body = await this.buildBaseRequestParams(deviceId);
|
|
570
|
+
body['user_details'] = JSON.stringify(userData);
|
|
571
|
+
await this.sendRequest(this.apiPath, body, 'User Details');
|
|
133
572
|
}
|
|
134
573
|
}
|
|
135
574
|
exports.default = BytemTracker.getInstance();
|