@bytem/bytem-tracker-app 0.0.3 → 0.0.7
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 +50 -25
- package/dist/src/BytemTracker.d.ts +58 -0
- package/dist/src/BytemTracker.js +474 -0
- package/dist/src/business/index.d.ts +4 -0
- package/dist/src/business/index.js +20 -0
- package/dist/src/business/trackCheckOutOrder.d.ts +2 -0
- package/dist/src/business/trackCheckOutOrder.js +20 -0
- package/dist/src/business/trackPayOrder.d.ts +2 -0
- package/dist/src/business/trackPayOrder.js +21 -0
- package/dist/src/business/trackUser.d.ts +2 -0
- package/dist/src/business/trackUser.js +13 -0
- package/dist/src/business/trackViewProduct.d.ts +2 -0
- package/dist/src/business/trackViewProduct.js +16 -0
- package/dist/src/core/base.d.ts +2 -0
- package/dist/src/core/base.js +24 -0
- package/dist/src/core/device.d.ts +3 -0
- package/dist/src/core/device.js +45 -0
- package/dist/src/core/request.d.ts +2 -0
- package/dist/src/core/request.js +21 -0
- package/dist/src/core/session.d.ts +17 -0
- package/dist/src/core/session.js +59 -0
- package/dist/src/core/storage.d.ts +9 -0
- package/dist/src/core/storage.js +41 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +22 -0
- package/dist/src/types.d.ts +67 -0
- package/dist/src/types.js +12 -0
- package/dist/test/BytemTracker.test.d.ts +1 -0
- package/dist/test/BytemTracker.test.js +248 -0
- package/dist/test/debug.test.d.ts +1 -0
- package/dist/test/debug.test.js +90 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.js +57 -0
- package/package.json +30 -11
- package/dist/env.d.ts +0 -1
- package/dist/env.js +0 -6
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -6
- package/dist/tracker.d.ts +0 -26
- package/dist/tracker.js +0 -120
- package/dist/types.d.ts +0 -24
- package/dist/types.js +0 -2
- package/dist/uuid.d.ts +0 -1
- package/dist/uuid.js +0 -19
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackPayOrder = void 0;
|
|
4
|
+
const trackPayOrder = (order) => {
|
|
5
|
+
return {
|
|
6
|
+
event: 'pay_order',
|
|
7
|
+
params: {
|
|
8
|
+
order_id: order.orderId,
|
|
9
|
+
total: order.total,
|
|
10
|
+
currency: order.currency,
|
|
11
|
+
products: order.products.map((p) => ({
|
|
12
|
+
product_id: p.productId,
|
|
13
|
+
name: p.name,
|
|
14
|
+
price: p.price,
|
|
15
|
+
quantity: p.quantity,
|
|
16
|
+
})),
|
|
17
|
+
status: 'success',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
exports.trackPayOrder = trackPayOrder;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackUser = void 0;
|
|
4
|
+
const trackUser = (userId, traits = {}) => {
|
|
5
|
+
return {
|
|
6
|
+
event: 'identify',
|
|
7
|
+
params: {
|
|
8
|
+
user_id: userId,
|
|
9
|
+
traits: traits,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
exports.trackUser = trackUser;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackViewProduct = void 0;
|
|
4
|
+
const trackViewProduct = (product) => {
|
|
5
|
+
return {
|
|
6
|
+
event: 'view_product',
|
|
7
|
+
params: {
|
|
8
|
+
product_id: product.productId,
|
|
9
|
+
name: product.name,
|
|
10
|
+
price: product.price,
|
|
11
|
+
currency: product.currency,
|
|
12
|
+
category: product.category,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
exports.trackViewProduct = trackViewProduct;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildBaseRequestParams = void 0;
|
|
4
|
+
const device_1 = require("./device");
|
|
5
|
+
const session_1 = require("./session");
|
|
6
|
+
const buildBaseRequestParams = async (appId, visitorId) => {
|
|
7
|
+
const deviceInfo = await (0, device_1.getDeviceInfo)();
|
|
8
|
+
const sessionInfo = (0, session_1.getSessionInfo)();
|
|
9
|
+
return {
|
|
10
|
+
app_id: appId,
|
|
11
|
+
device_id: visitorId,
|
|
12
|
+
session_id: sessionInfo ? sessionInfo.sessionId : '',
|
|
13
|
+
timestamp: Date.now(),
|
|
14
|
+
platform: deviceInfo.platform,
|
|
15
|
+
os: deviceInfo.os,
|
|
16
|
+
os_version: deviceInfo.osVersion,
|
|
17
|
+
model: deviceInfo.model,
|
|
18
|
+
language: deviceInfo.language,
|
|
19
|
+
screen_width: deviceInfo.screenWidth,
|
|
20
|
+
screen_height: deviceInfo.screenHeight,
|
|
21
|
+
user_agent: deviceInfo.userAgent,
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
exports.buildBaseRequestParams = buildBaseRequestParams;
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
exports.generateUUID = exports.getDeviceInfo = void 0;
|
|
7
|
+
const react_native_1 = require("react-native");
|
|
8
|
+
const react_native_device_info_1 = __importDefault(require("react-native-device-info"));
|
|
9
|
+
const getDeviceInfo = async () => {
|
|
10
|
+
var _a, _b, _c;
|
|
11
|
+
const os = react_native_device_info_1.default.getSystemName();
|
|
12
|
+
const osVersion = react_native_device_info_1.default.getSystemVersion();
|
|
13
|
+
const model = react_native_device_info_1.default.getModel();
|
|
14
|
+
const userAgent = await react_native_device_info_1.default.getUserAgent();
|
|
15
|
+
// Try to get language
|
|
16
|
+
let language = 'en';
|
|
17
|
+
if (react_native_1.Platform.OS === 'ios') {
|
|
18
|
+
const settings = (_a = react_native_1.NativeModules.SettingsManager) === null || _a === void 0 ? void 0 : _a.settings;
|
|
19
|
+
language = (settings === null || settings === void 0 ? void 0 : settings.AppleLocale) || ((_b = settings === null || settings === void 0 ? void 0 : settings.AppleLanguages) === null || _b === void 0 ? void 0 : _b[0]) || 'en';
|
|
20
|
+
}
|
|
21
|
+
else if (react_native_1.Platform.OS === 'android') {
|
|
22
|
+
language = ((_c = react_native_1.NativeModules.I18nManager) === null || _c === void 0 ? void 0 : _c.localeIdentifier) || 'en';
|
|
23
|
+
}
|
|
24
|
+
const { width, height } = react_native_1.Dimensions.get('window');
|
|
25
|
+
return {
|
|
26
|
+
platform: react_native_1.Platform.OS,
|
|
27
|
+
os,
|
|
28
|
+
osVersion,
|
|
29
|
+
model,
|
|
30
|
+
screenWidth: width,
|
|
31
|
+
screenHeight: height,
|
|
32
|
+
language,
|
|
33
|
+
userAgent,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
exports.getDeviceInfo = getDeviceInfo;
|
|
37
|
+
const generateUUID = () => {
|
|
38
|
+
// Simple UUID v4 generator that doesn't require crypto polyfills
|
|
39
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
40
|
+
const r = (Math.random() * 16) | 0;
|
|
41
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
42
|
+
return v.toString(16);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
exports.generateUUID = generateUUID;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendRequest = void 0;
|
|
4
|
+
const sendRequest = async (endpoint, data) => {
|
|
5
|
+
try {
|
|
6
|
+
const response = await fetch(endpoint, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
},
|
|
11
|
+
body: JSON.stringify(data),
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
console.error(`[BytemTracker] Request failed: ${response.statusText}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error('[BytemTracker] Network error:', error);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.sendRequest = sendRequest;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SessionInfo } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Initialize session. Must be called before tracking events.
|
|
4
|
+
* 1. Checks memory (fastest)
|
|
5
|
+
* 2. Checks storage
|
|
6
|
+
* 3. Creates new if none exists
|
|
7
|
+
*/
|
|
8
|
+
export declare const initSession: () => Promise<SessionInfo>;
|
|
9
|
+
/**
|
|
10
|
+
* Force start a new session
|
|
11
|
+
*/
|
|
12
|
+
export declare const refreshSession: () => Promise<SessionInfo>;
|
|
13
|
+
/**
|
|
14
|
+
* Get current session info.
|
|
15
|
+
* WARNING: Returns null if initSession() hasn't been called/awaited.
|
|
16
|
+
*/
|
|
17
|
+
export declare const getSessionInfo: () => SessionInfo | null;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSessionInfo = exports.refreshSession = exports.initSession = void 0;
|
|
4
|
+
const device_1 = require("./device");
|
|
5
|
+
const storage_1 = require("./storage");
|
|
6
|
+
let currentSession = null;
|
|
7
|
+
/**
|
|
8
|
+
* Initialize session. Must be called before tracking events.
|
|
9
|
+
* 1. Checks memory (fastest)
|
|
10
|
+
* 2. Checks storage
|
|
11
|
+
* 3. Creates new if none exists
|
|
12
|
+
*/
|
|
13
|
+
const initSession = async () => {
|
|
14
|
+
if (currentSession) {
|
|
15
|
+
return currentSession;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const storedId = await (0, storage_1.getItem)(storage_1.StorageKeys.SESSION_ID);
|
|
19
|
+
const storedStart = await (0, storage_1.getItem)(storage_1.StorageKeys.SESSION_START);
|
|
20
|
+
if (storedId && storedStart) {
|
|
21
|
+
currentSession = {
|
|
22
|
+
sessionId: storedId,
|
|
23
|
+
startTime: parseInt(storedStart, 10),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
await (0, exports.refreshSession)();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
// If error, generate new
|
|
32
|
+
await (0, exports.refreshSession)();
|
|
33
|
+
}
|
|
34
|
+
return currentSession;
|
|
35
|
+
};
|
|
36
|
+
exports.initSession = initSession;
|
|
37
|
+
/**
|
|
38
|
+
* Force start a new session
|
|
39
|
+
*/
|
|
40
|
+
const refreshSession = async () => {
|
|
41
|
+
const newId = (0, device_1.generateUUID)();
|
|
42
|
+
const newStart = Date.now();
|
|
43
|
+
currentSession = {
|
|
44
|
+
sessionId: newId,
|
|
45
|
+
startTime: newStart,
|
|
46
|
+
};
|
|
47
|
+
await (0, storage_1.setItem)(storage_1.StorageKeys.SESSION_ID, newId);
|
|
48
|
+
await (0, storage_1.setItem)(storage_1.StorageKeys.SESSION_START, newStart.toString());
|
|
49
|
+
return currentSession;
|
|
50
|
+
};
|
|
51
|
+
exports.refreshSession = refreshSession;
|
|
52
|
+
/**
|
|
53
|
+
* Get current session info.
|
|
54
|
+
* WARNING: Returns null if initSession() hasn't been called/awaited.
|
|
55
|
+
*/
|
|
56
|
+
const getSessionInfo = () => {
|
|
57
|
+
return currentSession;
|
|
58
|
+
};
|
|
59
|
+
exports.getSessionInfo = getSessionInfo;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const StorageKeys: {
|
|
2
|
+
VISITOR_ID: string;
|
|
3
|
+
DEVICE_ID: string;
|
|
4
|
+
SESSION_ID: string;
|
|
5
|
+
SESSION_START: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const getItem: (key: string) => Promise<string | null>;
|
|
8
|
+
export declare const setItem: (key: string, value: string) => Promise<void>;
|
|
9
|
+
export declare const removeItem: (key: string) => Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
exports.removeItem = exports.setItem = exports.getItem = exports.StorageKeys = void 0;
|
|
7
|
+
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
8
|
+
exports.StorageKeys = {
|
|
9
|
+
VISITOR_ID: 'bytem_visitor_id',
|
|
10
|
+
DEVICE_ID: 'bytem_device_id',
|
|
11
|
+
SESSION_ID: 'bytem_session_id',
|
|
12
|
+
SESSION_START: 'bytem_session_start',
|
|
13
|
+
};
|
|
14
|
+
const getItem = async (key) => {
|
|
15
|
+
try {
|
|
16
|
+
return await async_storage_1.default.getItem(key);
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
console.warn('[BytemTracker] Storage get error:', e);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
exports.getItem = getItem;
|
|
24
|
+
const setItem = async (key, value) => {
|
|
25
|
+
try {
|
|
26
|
+
await async_storage_1.default.setItem(key, value);
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
console.warn('[BytemTracker] Storage set error:', e);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
exports.setItem = setItem;
|
|
33
|
+
const removeItem = async (key) => {
|
|
34
|
+
try {
|
|
35
|
+
await async_storage_1.default.removeItem(key);
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
console.warn('[BytemTracker] Storage remove error:', e);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
exports.removeItem = removeItem;
|
|
@@ -0,0 +1,22 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
const BytemTracker_1 = __importDefault(require("./BytemTracker"));
|
|
21
|
+
__exportStar(require("./types"), exports);
|
|
22
|
+
exports.default = BytemTracker_1.default;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export interface TrackerConfig {
|
|
2
|
+
appId: string;
|
|
3
|
+
endpoint?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
visitorId?: string;
|
|
7
|
+
deviceId?: string;
|
|
8
|
+
appScheme?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const BytemEventKeys: {
|
|
11
|
+
nps: string;
|
|
12
|
+
survey: string;
|
|
13
|
+
starRating: string;
|
|
14
|
+
view: string;
|
|
15
|
+
orientation: string;
|
|
16
|
+
pushAction: string;
|
|
17
|
+
action: string;
|
|
18
|
+
};
|
|
19
|
+
export interface DeviceInfo {
|
|
20
|
+
platform: string;
|
|
21
|
+
os: string;
|
|
22
|
+
osVersion: string;
|
|
23
|
+
model: string;
|
|
24
|
+
screenWidth: number;
|
|
25
|
+
screenHeight: number;
|
|
26
|
+
language: string;
|
|
27
|
+
userAgent: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SessionInfo {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
startTime: number;
|
|
32
|
+
}
|
|
33
|
+
export interface BaseParams {
|
|
34
|
+
app_id: string;
|
|
35
|
+
device_id: string;
|
|
36
|
+
session_id: string;
|
|
37
|
+
timestamp: number;
|
|
38
|
+
platform: string;
|
|
39
|
+
os: string;
|
|
40
|
+
os_version: string;
|
|
41
|
+
model: string;
|
|
42
|
+
language: string;
|
|
43
|
+
screen_width: number;
|
|
44
|
+
screen_height: number;
|
|
45
|
+
user_agent: string;
|
|
46
|
+
}
|
|
47
|
+
export interface UserTraits {
|
|
48
|
+
[key: string]: any;
|
|
49
|
+
}
|
|
50
|
+
export interface Product {
|
|
51
|
+
productId: string;
|
|
52
|
+
name?: string;
|
|
53
|
+
price?: number;
|
|
54
|
+
currency?: string;
|
|
55
|
+
category?: string;
|
|
56
|
+
quantity?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface Order {
|
|
59
|
+
orderId: string;
|
|
60
|
+
total: number;
|
|
61
|
+
currency: string;
|
|
62
|
+
products: Product[];
|
|
63
|
+
}
|
|
64
|
+
export interface EventPayload {
|
|
65
|
+
event: string;
|
|
66
|
+
params: Record<string, any>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BytemEventKeys = void 0;
|
|
4
|
+
exports.BytemEventKeys = {
|
|
5
|
+
nps: "[CLY]_nps",
|
|
6
|
+
survey: "[CLY]_survey",
|
|
7
|
+
starRating: "[CLY]_star_rating",
|
|
8
|
+
view: "[CLY]_view",
|
|
9
|
+
orientation: "[CLY]_orientation",
|
|
10
|
+
pushAction: "[CLY]_push_action",
|
|
11
|
+
action: "[CLY]_action",
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,248 @@
|
|
|
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 storage_1 = require("../src/core/storage");
|
|
8
|
+
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
9
|
+
const types_1 = require("../src/types");
|
|
10
|
+
describe('BytemTracker SDK', () => {
|
|
11
|
+
const mockConfig = {
|
|
12
|
+
appId: 'test-app-id',
|
|
13
|
+
endpoint: 'https://api.example.com/track',
|
|
14
|
+
debug: true,
|
|
15
|
+
};
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
// Reset singleton instance state manually
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
BytemTracker_1.default.isInitialized = false;
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
BytemTracker_1.default.appKey = null;
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
BytemTracker_1.default.baseUrl = 'https://tracking.server.bytecon.com';
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
BytemTracker_1.default.visitorId = null;
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
BytemTracker_1.default.sessionStarted = false;
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
BytemTracker_1.default.lastBeat = null;
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
BytemTracker_1.default.trackTime = true;
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
BytemTracker_1.default.storedDuration = 0;
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
BytemTracker_1.default.lastViewTime = 0;
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
BytemTracker_1.default.lastViewStoredDuration = 0;
|
|
39
|
+
// Clear storage mocks
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
async_storage_1.default.clear();
|
|
42
|
+
});
|
|
43
|
+
describe('Initialization', () => {
|
|
44
|
+
it('should initialize correctly', async () => {
|
|
45
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
46
|
+
// Verify Storage was checked for Visitor ID
|
|
47
|
+
expect(async_storage_1.default.getItem).toHaveBeenCalledWith(storage_1.StorageKeys.VISITOR_ID);
|
|
48
|
+
// Verify Session was initialized (checks storage)
|
|
49
|
+
expect(async_storage_1.default.getItem).toHaveBeenCalledWith(`${mockConfig.appId}/cly_session`);
|
|
50
|
+
});
|
|
51
|
+
it('should use default endpoint when not provided', async () => {
|
|
52
|
+
await BytemTracker_1.default.init({
|
|
53
|
+
appId: 'test-app-id',
|
|
54
|
+
// endpoint is omitted
|
|
55
|
+
debug: true,
|
|
56
|
+
});
|
|
57
|
+
await BytemTracker_1.default.trackEvent('test_event', {});
|
|
58
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
59
|
+
const calls = global.fetch.mock.calls;
|
|
60
|
+
// Check any call, they should all use the default endpoint
|
|
61
|
+
const url = calls[0][0];
|
|
62
|
+
expect(url).toBe('https://tracking.server.bytecon.com/i');
|
|
63
|
+
});
|
|
64
|
+
it('should override endpoint path when path is configured', async () => {
|
|
65
|
+
await BytemTracker_1.default.init({
|
|
66
|
+
appId: 'test-app-id-2',
|
|
67
|
+
endpoint: 'https://api.example.com/track',
|
|
68
|
+
path: '/collect',
|
|
69
|
+
debug: true,
|
|
70
|
+
});
|
|
71
|
+
await BytemTracker_1.default.trackEvent('test_event', {});
|
|
72
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
73
|
+
const calls = global.fetch.mock.calls;
|
|
74
|
+
const url = calls[0][0];
|
|
75
|
+
expect(url).toBe('https://api.example.com/track/collect');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('Event Tracking', () => {
|
|
79
|
+
beforeEach(async () => {
|
|
80
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
81
|
+
});
|
|
82
|
+
it('should track a simple event', async () => {
|
|
83
|
+
const eventName = 'test_event';
|
|
84
|
+
const segmentation = { foo: 'bar' };
|
|
85
|
+
await BytemTracker_1.default.trackEvent(eventName, segmentation);
|
|
86
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
87
|
+
const calls = global.fetch.mock.calls;
|
|
88
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
89
|
+
expect(eventCall).toBeDefined();
|
|
90
|
+
const options = eventCall[1];
|
|
91
|
+
const body = options.body;
|
|
92
|
+
expect(body).toContain(`app_key=${mockConfig.appId}`);
|
|
93
|
+
expect(body).toContain('events=%5B%7B%22key%22%3A%22test_event%22');
|
|
94
|
+
});
|
|
95
|
+
it('should track event with count and sum', async () => {
|
|
96
|
+
const eventName = 'purchase';
|
|
97
|
+
const count = 3;
|
|
98
|
+
const sum = 99.99;
|
|
99
|
+
const segmentation = { item: 'apple' };
|
|
100
|
+
await BytemTracker_1.default.trackEvent(eventName, segmentation, count, sum);
|
|
101
|
+
const calls = global.fetch.mock.calls;
|
|
102
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
103
|
+
const body = decodeURIComponent(eventCall[1].body);
|
|
104
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
105
|
+
expect(events[0]).toMatchObject({
|
|
106
|
+
key: eventName,
|
|
107
|
+
count: count,
|
|
108
|
+
sum: sum,
|
|
109
|
+
segmentation: segmentation
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
it('should track event with duration', async () => {
|
|
113
|
+
const eventName = 'video_watched';
|
|
114
|
+
const duration = 120; // seconds
|
|
115
|
+
await BytemTracker_1.default.trackEvent(eventName, {}, 1, undefined, duration);
|
|
116
|
+
const calls = global.fetch.mock.calls;
|
|
117
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
118
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
119
|
+
expect(events[0].dur).toBe(duration);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('Page View Tracking', () => {
|
|
123
|
+
beforeEach(async () => {
|
|
124
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
125
|
+
});
|
|
126
|
+
it('should track page view', async () => {
|
|
127
|
+
const pageName = '/home';
|
|
128
|
+
await BytemTracker_1.default.trackPageview(pageName);
|
|
129
|
+
const calls = global.fetch.mock.calls;
|
|
130
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes(`%22key%22%3A%22${encodeURIComponent(types_1.BytemEventKeys.view)}%22`));
|
|
131
|
+
expect(eventCall).toBeDefined();
|
|
132
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
133
|
+
expect(events[0].segmentation).toMatchObject({
|
|
134
|
+
name: pageName,
|
|
135
|
+
current: pageName,
|
|
136
|
+
referrer: ''
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
it('should track page view with referrer', async () => {
|
|
140
|
+
const pageName = '/product/123';
|
|
141
|
+
const referrer = '/home';
|
|
142
|
+
await BytemTracker_1.default.trackPageview(pageName, referrer);
|
|
143
|
+
const calls = global.fetch.mock.calls;
|
|
144
|
+
// Get the last call which should be the page view
|
|
145
|
+
const eventCall = calls[calls.length - 1];
|
|
146
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
147
|
+
expect(events[0].segmentation).toMatchObject({
|
|
148
|
+
name: pageName,
|
|
149
|
+
current: pageName,
|
|
150
|
+
referrer: referrer
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe('User Tracking', () => {
|
|
155
|
+
beforeEach(async () => {
|
|
156
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
157
|
+
});
|
|
158
|
+
it('should track user details', async () => {
|
|
159
|
+
const userId = 'user_123';
|
|
160
|
+
const userTraits = {
|
|
161
|
+
name: 'John Doe',
|
|
162
|
+
email: 'john@example.com',
|
|
163
|
+
custom: {
|
|
164
|
+
plan: 'premium'
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
await BytemTracker_1.default.trackUser(userId, userTraits);
|
|
168
|
+
const calls = global.fetch.mock.calls;
|
|
169
|
+
const userCall = calls.find(call => call[1].body && call[1].body.includes('user_details='));
|
|
170
|
+
expect(userCall).toBeDefined();
|
|
171
|
+
const userDetails = JSON.parse(new URLSearchParams(userCall[1].body).get('user_details') || '{}');
|
|
172
|
+
expect(userDetails).toMatchObject({
|
|
173
|
+
name: 'John Doe',
|
|
174
|
+
email: 'john@example.com',
|
|
175
|
+
custom: {
|
|
176
|
+
plan: 'premium',
|
|
177
|
+
user_id: userId
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe('Session Management', () => {
|
|
183
|
+
beforeEach(async () => {
|
|
184
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
185
|
+
});
|
|
186
|
+
it('should track session start', async () => {
|
|
187
|
+
await BytemTracker_1.default.trackSessions();
|
|
188
|
+
const calls = global.fetch.mock.calls;
|
|
189
|
+
const sessionCall = calls.find(call => call[1].body && call[1].body.includes('begin_session=1'));
|
|
190
|
+
expect(sessionCall).toBeDefined();
|
|
191
|
+
});
|
|
192
|
+
it('should track session end', async () => {
|
|
193
|
+
// First start session
|
|
194
|
+
await BytemTracker_1.default.trackSessions();
|
|
195
|
+
// Then end session
|
|
196
|
+
await BytemTracker_1.default.endSession(undefined, true); // force=true to bypass cookie check for test
|
|
197
|
+
const calls = global.fetch.mock.calls;
|
|
198
|
+
const endSessionCall = calls.find(call => call[1].body && call[1].body.includes('end_session=1'));
|
|
199
|
+
expect(endSessionCall).toBeDefined();
|
|
200
|
+
const body = new URLSearchParams(endSessionCall[1].body);
|
|
201
|
+
expect(body.has('session_duration')).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
describe('E-commerce Tracking', () => {
|
|
205
|
+
beforeEach(async () => {
|
|
206
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
207
|
+
});
|
|
208
|
+
it('should track checkout order', async () => {
|
|
209
|
+
const order = {
|
|
210
|
+
orderId: 'order_123',
|
|
211
|
+
total: 99.99,
|
|
212
|
+
currency: 'USD',
|
|
213
|
+
products: [
|
|
214
|
+
{ productId: 'prod_1', price: 50, quantity: 1 },
|
|
215
|
+
{ productId: 'prod_2', price: 49.99, quantity: 1 }
|
|
216
|
+
]
|
|
217
|
+
};
|
|
218
|
+
await BytemTracker_1.default.trackCheckOutOrder(order);
|
|
219
|
+
const calls = global.fetch.mock.calls;
|
|
220
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
221
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
222
|
+
expect(events[0].key).toBe('check_out_order');
|
|
223
|
+
expect(events[0].segmentation).toMatchObject({
|
|
224
|
+
order_id: 'order_123',
|
|
225
|
+
total: 99.99,
|
|
226
|
+
currency: 'USD'
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
it('should track pay order', async () => {
|
|
230
|
+
const order = {
|
|
231
|
+
orderId: 'order_123',
|
|
232
|
+
total: 99.99,
|
|
233
|
+
currency: 'USD',
|
|
234
|
+
products: []
|
|
235
|
+
};
|
|
236
|
+
await BytemTracker_1.default.trackPayOrder(order);
|
|
237
|
+
const calls = global.fetch.mock.calls;
|
|
238
|
+
const eventCall = calls.find(call => call[1].body && call[1].body.includes('events='));
|
|
239
|
+
const events = JSON.parse(new URLSearchParams(eventCall[1].body).get('events') || '[]');
|
|
240
|
+
expect(events[0].key).toBe('pay_order');
|
|
241
|
+
expect(events[0].segmentation).toMatchObject({
|
|
242
|
+
order_id: 'order_123',
|
|
243
|
+
total: 99.99,
|
|
244
|
+
currency: 'USD'
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|