@bytem/bytem-tracker-app 0.0.3 → 0.0.6
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 +138 -25
- package/dist/src/BytemTracker.d.ts +18 -0
- package/dist/src/BytemTracker.js +135 -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 +56 -0
- package/dist/test/BytemTracker.test.d.ts +1 -0
- package/dist/test/BytemTracker.test.js +84 -0
- package/dist/test/debug.test.d.ts +1 -0
- package/dist/test/debug.test.js +58 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.js +56 -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/uuid.d.ts +0 -1
- package/dist/uuid.js +0 -19
- /package/dist/{types.js → src/types.js} +0 -0
package/README.md
CHANGED
|
@@ -1,41 +1,154 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bytem Tracker SDK (React Native)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This is the official Bytem Tracker SDK for React Native applications.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the package and its peer dependencies using Yarn:
|
|
7
8
|
|
|
8
|
-
### 此项目运行起来
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
yarn install
|
|
12
|
-
yarn build
|
|
10
|
+
yarn add @bytem/bytem-tracker-app @react-native-async-storage/async-storage react-native-device-info uuid
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
### iOS Setup
|
|
14
|
+
Don't forget to install pods for iOS:
|
|
15
|
+
```bash
|
|
16
|
+
cd ios && pod install
|
|
17
|
+
```
|
|
16
18
|
|
|
17
|
-
##
|
|
19
|
+
## Development
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
- ✅ 行为 100% 对齐 Flutter SDK
|
|
21
|
-
- ✅ 单例模式
|
|
22
|
-
- ✅ 自动 deviceId / visitorId 管理
|
|
23
|
-
- ✅ TypeScript 支持
|
|
24
|
-
- ❌ 不支持 Web(刻意限制,避免误用)
|
|
21
|
+
This project uses **Yarn** for dependency management.
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
### Setup
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
```bash
|
|
26
|
+
yarn install
|
|
27
|
+
```
|
|
29
28
|
|
|
30
|
-
###
|
|
29
|
+
### Running Tests
|
|
31
30
|
|
|
32
31
|
```bash
|
|
33
|
-
yarn
|
|
34
|
-
yarn add @react-native-async-storage/async-storage
|
|
32
|
+
yarn test
|
|
35
33
|
```
|
|
36
34
|
|
|
37
|
-
###
|
|
35
|
+
### Build
|
|
36
|
+
|
|
38
37
|
```bash
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
yarn build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Release & Publish
|
|
42
|
+
|
|
43
|
+
Automate version bump and publish via npm scripts:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Patch release (1.0.0 -> 1.0.1)
|
|
47
|
+
npm run release:patch
|
|
48
|
+
|
|
49
|
+
# Minor release (1.0.0 -> 1.1.0)
|
|
50
|
+
npm run release:minor
|
|
51
|
+
|
|
52
|
+
# Major release (1.0.0 -> 2.0.0)
|
|
53
|
+
npm run release:major
|
|
54
|
+
|
|
55
|
+
# Beta prerelease (1.0.0 -> 1.0.1-beta.0)
|
|
56
|
+
npm run release:beta
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Notes:
|
|
60
|
+
- These scripts bump package.json version and publish to npm.
|
|
61
|
+
- `prepublishOnly` builds the package before publishing.
|
|
62
|
+
- Configure auth via user-level `~/.npmrc`. Do not commit tokens in the repo.
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
### Initialization
|
|
66
|
+
Initialize the tracker in your app entry point (e.g., `App.tsx` or `index.js`). Note that `init` is asynchronous.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import React, { useEffect } from 'react';
|
|
70
|
+
import BytemTracker from '@bytem/bytem-tracker-app';
|
|
71
|
+
|
|
72
|
+
const App = () => {
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const initTracker = async () => {
|
|
75
|
+
await BytemTracker.init({
|
|
76
|
+
appId: 'your-app-id',
|
|
77
|
+
endpoint: 'https://api.bytem.com/track',
|
|
78
|
+
debug: __DEV__, // Enable debug logs in development
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
initTracker();
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
return <YourApp />;
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Tracking Events
|
|
90
|
+
|
|
91
|
+
#### Identify User
|
|
92
|
+
Track user identification when a user logs in or updates their profile.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// Signature: trackUser(userId: string, traits?: object)
|
|
96
|
+
BytemTracker.trackUser('user_12345', {
|
|
97
|
+
email: 'user@example.com',
|
|
98
|
+
age: 25,
|
|
99
|
+
membership: 'gold'
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### View Product
|
|
104
|
+
Track when a user views a product details page.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
BytemTracker.trackViewProduct({
|
|
108
|
+
productId: 'prod_001',
|
|
109
|
+
name: 'Awesome Gadget',
|
|
110
|
+
price: 99.99,
|
|
111
|
+
currency: 'USD',
|
|
112
|
+
category: 'Electronics'
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Checkout Order
|
|
117
|
+
Track when a user initiates checkout.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
BytemTracker.trackCheckOutOrder({
|
|
121
|
+
orderId: 'order_abc123',
|
|
122
|
+
total: 150.00,
|
|
123
|
+
currency: 'USD',
|
|
124
|
+
products: [
|
|
125
|
+
{ productId: 'prod_001', price: 99.99, quantity: 1 },
|
|
126
|
+
{ productId: 'prod_002', price: 50.01, quantity: 1 }
|
|
127
|
+
]
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Pay Order
|
|
132
|
+
Track when a user completes a payment.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
BytemTracker.trackPayOrder({
|
|
136
|
+
orderId: 'order_abc123',
|
|
137
|
+
total: 150.00,
|
|
138
|
+
currency: 'USD',
|
|
139
|
+
products: [
|
|
140
|
+
{ productId: 'prod_001', price: 99.99, quantity: 1 },
|
|
141
|
+
{ productId: 'prod_002', price: 50.01, quantity: 1 }
|
|
142
|
+
]
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Platform Compatibility
|
|
147
|
+
|
|
148
|
+
This SDK is designed for **React Native** and supports:
|
|
149
|
+
- iOS
|
|
150
|
+
- Android
|
|
151
|
+
|
|
152
|
+
It relies on:
|
|
153
|
+
- `@react-native-async-storage/async-storage`: For persisting session and visitor IDs.
|
|
154
|
+
- `react-native-device-info`: For capturing device metrics (model, OS version, etc.).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TrackerConfig, Product, Order, UserTraits } from './types';
|
|
2
|
+
declare class BytemTracker {
|
|
3
|
+
private static instance;
|
|
4
|
+
private config;
|
|
5
|
+
private isInitialized;
|
|
6
|
+
private visitorId;
|
|
7
|
+
private readonly DEFAULT_ENDPOINT;
|
|
8
|
+
private constructor();
|
|
9
|
+
static getInstance(): BytemTracker;
|
|
10
|
+
init(config: TrackerConfig): Promise<void>;
|
|
11
|
+
private track;
|
|
12
|
+
trackViewProduct(product: Product): void;
|
|
13
|
+
trackCheckOutOrder(order: Order): void;
|
|
14
|
+
trackPayOrder(order: Order): void;
|
|
15
|
+
trackUser(userId: string, traits?: UserTraits): void;
|
|
16
|
+
}
|
|
17
|
+
declare const _default: BytemTracker;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const base_1 = require("./core/base");
|
|
37
|
+
const request_1 = require("./core/request");
|
|
38
|
+
const Business = __importStar(require("./business"));
|
|
39
|
+
const session_1 = require("./core/session");
|
|
40
|
+
const storage_1 = require("./core/storage");
|
|
41
|
+
const device_1 = require("./core/device");
|
|
42
|
+
class BytemTracker {
|
|
43
|
+
constructor() {
|
|
44
|
+
this.config = null;
|
|
45
|
+
this.isInitialized = false;
|
|
46
|
+
this.visitorId = null;
|
|
47
|
+
this.DEFAULT_ENDPOINT = 'https://tracking.server.bytecon.com/i';
|
|
48
|
+
}
|
|
49
|
+
static getInstance() {
|
|
50
|
+
if (!BytemTracker.instance) {
|
|
51
|
+
BytemTracker.instance = new BytemTracker();
|
|
52
|
+
}
|
|
53
|
+
return BytemTracker.instance;
|
|
54
|
+
}
|
|
55
|
+
async init(config) {
|
|
56
|
+
if (this.isInitialized) {
|
|
57
|
+
console.warn('[BytemTracker] Already initialized');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.config = config;
|
|
61
|
+
this.isInitialized = true;
|
|
62
|
+
// Initialize Visitor ID
|
|
63
|
+
if (config.visitorId) {
|
|
64
|
+
this.visitorId = config.visitorId;
|
|
65
|
+
await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.visitorId = await (0, storage_1.getItem)(storage_1.StorageKeys.VISITOR_ID);
|
|
69
|
+
if (!this.visitorId) {
|
|
70
|
+
this.visitorId = (0, device_1.generateUUID)();
|
|
71
|
+
await (0, storage_1.setItem)(storage_1.StorageKeys.VISITOR_ID, this.visitorId);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Initialize Session
|
|
75
|
+
await (0, session_1.initSession)();
|
|
76
|
+
if (!this.config.endpoint) {
|
|
77
|
+
this.config.endpoint = this.DEFAULT_ENDPOINT;
|
|
78
|
+
}
|
|
79
|
+
if (config.debug) {
|
|
80
|
+
console.log('[BytemTracker] Initialized with config:', this.config);
|
|
81
|
+
console.log('[BytemTracker] Visitor ID:', this.visitorId);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async track(payload) {
|
|
85
|
+
if (!this.isInitialized || !this.config || !this.visitorId) {
|
|
86
|
+
console.error('[BytemTracker] Not initialized. Call await init() first.');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const baseParams = await (0, base_1.buildBaseRequestParams)(this.config.appId, this.visitorId);
|
|
90
|
+
const finalData = Object.assign(Object.assign({}, baseParams), payload);
|
|
91
|
+
if (this.config.debug) {
|
|
92
|
+
console.log('[BytemTracker] Tracking event:', finalData);
|
|
93
|
+
}
|
|
94
|
+
if (this.config.endpoint) {
|
|
95
|
+
let requestUrl = this.config.endpoint;
|
|
96
|
+
try {
|
|
97
|
+
const urlObj = new URL(this.config.endpoint);
|
|
98
|
+
const hasEndpointPath = urlObj.pathname && urlObj.pathname !== '/';
|
|
99
|
+
const pathToUse = this.config.path;
|
|
100
|
+
if (pathToUse) {
|
|
101
|
+
urlObj.pathname = pathToUse.startsWith('/') ? pathToUse : '/' + pathToUse;
|
|
102
|
+
requestUrl = urlObj.toString();
|
|
103
|
+
}
|
|
104
|
+
else if (!hasEndpointPath) {
|
|
105
|
+
urlObj.pathname = '/i';
|
|
106
|
+
requestUrl = urlObj.toString();
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
requestUrl = this.config.endpoint;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (_a) {
|
|
113
|
+
requestUrl = this.config.endpoint;
|
|
114
|
+
}
|
|
115
|
+
await (0, request_1.sendRequest)(requestUrl, finalData);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
trackViewProduct(product) {
|
|
119
|
+
const payload = Business.trackViewProduct(product);
|
|
120
|
+
this.track(payload);
|
|
121
|
+
}
|
|
122
|
+
trackCheckOutOrder(order) {
|
|
123
|
+
const payload = Business.trackCheckOutOrder(order);
|
|
124
|
+
this.track(payload);
|
|
125
|
+
}
|
|
126
|
+
trackPayOrder(order) {
|
|
127
|
+
const payload = Business.trackPayOrder(order);
|
|
128
|
+
this.track(payload);
|
|
129
|
+
}
|
|
130
|
+
trackUser(userId, traits) {
|
|
131
|
+
const payload = Business.trackUser(userId, traits);
|
|
132
|
+
this.track(payload);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.default = BytemTracker.getInstance();
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./trackViewProduct"), exports);
|
|
18
|
+
__exportStar(require("./trackCheckOutOrder"), exports);
|
|
19
|
+
__exportStar(require("./trackPayOrder"), exports);
|
|
20
|
+
__exportStar(require("./trackUser"), exports);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackCheckOutOrder = void 0;
|
|
4
|
+
const trackCheckOutOrder = (order) => {
|
|
5
|
+
return {
|
|
6
|
+
event: 'checkout_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
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
exports.trackCheckOutOrder = trackCheckOutOrder;
|
|
@@ -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,56 @@
|
|
|
1
|
+
export interface TrackerConfig {
|
|
2
|
+
appId: string;
|
|
3
|
+
endpoint?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
visitorId?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DeviceInfo {
|
|
9
|
+
platform: string;
|
|
10
|
+
os: string;
|
|
11
|
+
osVersion: string;
|
|
12
|
+
model: string;
|
|
13
|
+
screenWidth: number;
|
|
14
|
+
screenHeight: number;
|
|
15
|
+
language: string;
|
|
16
|
+
userAgent: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SessionInfo {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
startTime: number;
|
|
21
|
+
}
|
|
22
|
+
export interface BaseParams {
|
|
23
|
+
app_id: string;
|
|
24
|
+
device_id: string;
|
|
25
|
+
session_id: string;
|
|
26
|
+
timestamp: number;
|
|
27
|
+
platform: string;
|
|
28
|
+
os: string;
|
|
29
|
+
os_version: string;
|
|
30
|
+
model: string;
|
|
31
|
+
language: string;
|
|
32
|
+
screen_width: number;
|
|
33
|
+
screen_height: number;
|
|
34
|
+
user_agent: string;
|
|
35
|
+
}
|
|
36
|
+
export interface UserTraits {
|
|
37
|
+
[key: string]: any;
|
|
38
|
+
}
|
|
39
|
+
export interface Product {
|
|
40
|
+
productId: string;
|
|
41
|
+
name?: string;
|
|
42
|
+
price?: number;
|
|
43
|
+
currency?: string;
|
|
44
|
+
category?: string;
|
|
45
|
+
quantity?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface Order {
|
|
48
|
+
orderId: string;
|
|
49
|
+
total: number;
|
|
50
|
+
currency: string;
|
|
51
|
+
products: Product[];
|
|
52
|
+
}
|
|
53
|
+
export interface EventPayload {
|
|
54
|
+
event: string;
|
|
55
|
+
params: Record<string, any>;
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
describe('BytemTracker SDK', () => {
|
|
10
|
+
const mockConfig = {
|
|
11
|
+
appId: 'test-app-id',
|
|
12
|
+
endpoint: 'https://api.example.com/track',
|
|
13
|
+
debug: true,
|
|
14
|
+
};
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
// Reset singleton instance manually
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
BytemTracker_1.default.instance = undefined;
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
BytemTracker_1.default.isInitialized = false;
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
BytemTracker_1.default.config = null;
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
BytemTracker_1.default.visitorId = null;
|
|
26
|
+
});
|
|
27
|
+
it('should initialize correctly', async () => {
|
|
28
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
29
|
+
// Verify Storage was checked for Visitor ID
|
|
30
|
+
expect(async_storage_1.default.getItem).toHaveBeenCalledWith(storage_1.StorageKeys.VISITOR_ID);
|
|
31
|
+
// Verify Session was initialized (checks storage)
|
|
32
|
+
expect(async_storage_1.default.getItem).toHaveBeenCalledWith(storage_1.StorageKeys.SESSION_ID);
|
|
33
|
+
});
|
|
34
|
+
it('should track an event', async () => {
|
|
35
|
+
// Ensure initialized (singleton persists)
|
|
36
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
37
|
+
const eventName = 'test_event';
|
|
38
|
+
const eventParams = { foo: 'bar' };
|
|
39
|
+
// @ts-ignore - Accessing private method for testing or just use public track methods
|
|
40
|
+
// Since track is private, we'll use a public method like trackUser or just trust the public API
|
|
41
|
+
// Let's use trackUser as a proxy for generic tracking
|
|
42
|
+
BytemTracker_1.default.trackUser('user-123', { age: 25 });
|
|
43
|
+
// Wait for async operations if any (track is async but void return in public API might hide it)
|
|
44
|
+
// In BytemTracker.ts, trackUser calls track, which is async but not awaited by caller.
|
|
45
|
+
// We need to wait for promises to resolve.
|
|
46
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
47
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
48
|
+
const fetchCall = global.fetch.mock.calls[0];
|
|
49
|
+
const url = fetchCall[0];
|
|
50
|
+
const options = fetchCall[1];
|
|
51
|
+
expect(url).toBe(mockConfig.endpoint);
|
|
52
|
+
expect(options.method).toBe('POST');
|
|
53
|
+
const body = JSON.parse(options.body);
|
|
54
|
+
expect(body.app_id).toBe(mockConfig.appId);
|
|
55
|
+
// expect(body.event).toBe('identify');
|
|
56
|
+
});
|
|
57
|
+
it('should use default endpoint when not provided', async () => {
|
|
58
|
+
await BytemTracker_1.default.init({
|
|
59
|
+
appId: 'test-app-id',
|
|
60
|
+
// endpoint is omitted
|
|
61
|
+
debug: true,
|
|
62
|
+
});
|
|
63
|
+
BytemTracker_1.default.trackUser('user-123', { age: 25 });
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
65
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
66
|
+
const fetchCall = global.fetch.mock.calls[0];
|
|
67
|
+
const url = fetchCall[0];
|
|
68
|
+
expect(url).toBe('https://tracking.server.bytecon.com/i');
|
|
69
|
+
});
|
|
70
|
+
it('should override endpoint path when path is configured', async () => {
|
|
71
|
+
await BytemTracker_1.default.init({
|
|
72
|
+
appId: 'test-app-id-2',
|
|
73
|
+
endpoint: 'https://api.example.com/track',
|
|
74
|
+
path: '/collect',
|
|
75
|
+
debug: true,
|
|
76
|
+
});
|
|
77
|
+
BytemTracker_1.default.trackUser('user-456', { age: 30 });
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
79
|
+
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
80
|
+
const fetchCall = global.fetch.mock.calls[0];
|
|
81
|
+
const url = fetchCall[0];
|
|
82
|
+
expect(url).toBe('https://api.example.com/collect');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
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('Debug Scenarios', () => {
|
|
9
|
+
const mockConfig = {
|
|
10
|
+
appId: 'debug-app-id',
|
|
11
|
+
endpoint: 'https://debug-api.bytem.com/track',
|
|
12
|
+
debug: true,
|
|
13
|
+
};
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
// Simulate fresh start
|
|
17
|
+
async_storage_1.default.getItem.mockResolvedValue(null);
|
|
18
|
+
await BytemTracker_1.default.init(mockConfig);
|
|
19
|
+
});
|
|
20
|
+
it('Scenario: User Journey - View Product -> Add to Cart -> Checkout', async () => {
|
|
21
|
+
// 1. View Product
|
|
22
|
+
console.log('>>> Step 1: Tracking View Product');
|
|
23
|
+
BytemTracker_1.default.trackViewProduct({
|
|
24
|
+
productId: 'p_1001',
|
|
25
|
+
name: 'Debug Smartphone',
|
|
26
|
+
price: 699.00,
|
|
27
|
+
currency: 'USD',
|
|
28
|
+
category: 'Electronics'
|
|
29
|
+
});
|
|
30
|
+
// Wait for async track
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
32
|
+
// 2. Identify User (Simulate Login)
|
|
33
|
+
console.log('>>> Step 2: User Login');
|
|
34
|
+
BytemTracker_1.default.trackUser('user_debug_001', {
|
|
35
|
+
plan: 'premium',
|
|
36
|
+
login_method: 'email'
|
|
37
|
+
});
|
|
38
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
39
|
+
// 3. Checkout
|
|
40
|
+
console.log('>>> Step 3: Checkout');
|
|
41
|
+
BytemTracker_1.default.trackCheckOutOrder({
|
|
42
|
+
orderId: 'order_999',
|
|
43
|
+
total: 699.00,
|
|
44
|
+
currency: 'USD',
|
|
45
|
+
products: [
|
|
46
|
+
{ productId: 'p_1001', price: 699.00, quantity: 1 }
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
50
|
+
// Assertions to verify flow
|
|
51
|
+
expect(global.fetch).toHaveBeenCalledTimes(3);
|
|
52
|
+
const calls = global.fetch.mock.calls;
|
|
53
|
+
// Check Event Types
|
|
54
|
+
const events = calls.map((call) => JSON.parse(call[1].body).event);
|
|
55
|
+
console.log('>>> Captured Events:', events);
|
|
56
|
+
expect(events).toEqual(['view_product', 'identify', 'checkout_order']);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare const mockStorage: Record<string, string>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Mock AsyncStorage
|
|
3
|
+
const mockStorage = {};
|
|
4
|
+
jest.mock('@react-native-async-storage/async-storage', () => ({
|
|
5
|
+
setItem: jest.fn((key, value) => {
|
|
6
|
+
mockStorage[key] = value;
|
|
7
|
+
return Promise.resolve(null);
|
|
8
|
+
}),
|
|
9
|
+
getItem: jest.fn((key) => {
|
|
10
|
+
return Promise.resolve(mockStorage[key] || null);
|
|
11
|
+
}),
|
|
12
|
+
removeItem: jest.fn((key) => {
|
|
13
|
+
delete mockStorage[key];
|
|
14
|
+
return Promise.resolve(null);
|
|
15
|
+
}),
|
|
16
|
+
clear: jest.fn(() => {
|
|
17
|
+
for (const key in mockStorage)
|
|
18
|
+
delete mockStorage[key];
|
|
19
|
+
return Promise.resolve(null);
|
|
20
|
+
}),
|
|
21
|
+
}));
|
|
22
|
+
// Mock React Native Device Info
|
|
23
|
+
jest.mock('react-native-device-info', () => ({
|
|
24
|
+
getSystemName: jest.fn(() => 'iOS'),
|
|
25
|
+
getSystemVersion: jest.fn(() => '14.0'),
|
|
26
|
+
getModel: jest.fn(() => 'iPhone 11'),
|
|
27
|
+
getUserAgent: jest.fn(() => Promise.resolve('Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)')),
|
|
28
|
+
}));
|
|
29
|
+
// Mock React Native
|
|
30
|
+
jest.mock('react-native', () => ({
|
|
31
|
+
Platform: {
|
|
32
|
+
OS: 'ios',
|
|
33
|
+
select: jest.fn(),
|
|
34
|
+
},
|
|
35
|
+
Dimensions: {
|
|
36
|
+
get: jest.fn(() => ({ width: 375, height: 812 })),
|
|
37
|
+
},
|
|
38
|
+
NativeModules: {
|
|
39
|
+
SettingsManager: {
|
|
40
|
+
settings: {
|
|
41
|
+
AppleLocale: 'en_US',
|
|
42
|
+
AppleLanguages: ['en-US'],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
I18nManager: {
|
|
46
|
+
localeIdentifier: 'en_US',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
// Mock global fetch
|
|
51
|
+
global.fetch = jest.fn(() => Promise.resolve({
|
|
52
|
+
ok: true,
|
|
53
|
+
status: 200,
|
|
54
|
+
statusText: 'OK',
|
|
55
|
+
json: () => Promise.resolve({}),
|
|
56
|
+
}));
|
package/package.json
CHANGED
|
@@ -1,31 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytem/bytem-tracker-app",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"description": "Bytem Tracker SDK for React Native",
|
|
4
5
|
"main": "dist/index.js",
|
|
5
6
|
"types": "dist/index.d.ts",
|
|
6
7
|
"files": [
|
|
7
8
|
"dist"
|
|
8
9
|
],
|
|
9
|
-
"description": "bytem SDK for React Native",
|
|
10
|
-
"license": "ISC",
|
|
11
|
-
"author": "Barry",
|
|
12
10
|
"scripts": {
|
|
13
11
|
"build": "tsc",
|
|
14
|
-
"
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"release:patch": "npm version patch && npm publish",
|
|
15
|
+
"release:minor": "npm version minor && npm publish",
|
|
16
|
+
"release:major": "npm version major && npm publish",
|
|
17
|
+
"release:beta": "npm version prerelease --preid=beta && npm publish --tag beta"
|
|
15
18
|
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"tracker",
|
|
21
|
+
"sdk",
|
|
22
|
+
"analytics",
|
|
23
|
+
"react-native"
|
|
24
|
+
],
|
|
25
|
+
"author": "Barry",
|
|
26
|
+
"license": "ISC",
|
|
16
27
|
"peerDependencies": {
|
|
17
|
-
"react-native": "
|
|
28
|
+
"@react-native-async-storage/async-storage": "^1.0.0",
|
|
29
|
+
"react": ">=16.8.0",
|
|
30
|
+
"react-native": ">=0.60.0",
|
|
31
|
+
"react-native-device-info": "^10.0.0"
|
|
18
32
|
},
|
|
19
33
|
"dependencies": {
|
|
20
|
-
"@react-native-async-storage/async-storage": "^1.21.0",
|
|
21
34
|
"uuid": "^9.0.0"
|
|
22
35
|
},
|
|
23
36
|
"devDependencies": {
|
|
24
|
-
"@
|
|
37
|
+
"@react-native-async-storage/async-storage": "^1.19.0",
|
|
38
|
+
"@types/jest": "^30.0.0",
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"@types/react": "^18.0.0",
|
|
41
|
+
"@types/react-native": "^0.72.0",
|
|
25
42
|
"@types/uuid": "^9.0.0",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
43
|
+
"jest": "^30.2.0",
|
|
44
|
+
"react": "18.2.0",
|
|
45
|
+
"react-native": "0.72.0",
|
|
46
|
+
"react-native-device-info": "^10.8.0",
|
|
47
|
+
"ts-jest": "^29.4.6",
|
|
29
48
|
"typescript": "^5.0.0"
|
|
30
49
|
},
|
|
31
50
|
"publishConfig": {
|
package/dist/env.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function isExpoRuntime(): boolean;
|
package/dist/env.js
DELETED
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BytemTracker = void 0;
|
|
4
|
-
const tracker_1 = require("./tracker");
|
|
5
|
-
Object.defineProperty(exports, "BytemTracker", { enumerable: true, get: function () { return tracker_1.BytemTracker; } });
|
|
6
|
-
exports.default = tracker_1.BytemTracker.instance;
|
package/dist/tracker.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export declare class BytemTracker {
|
|
2
|
-
private static _instance;
|
|
3
|
-
static get instance(): BytemTracker;
|
|
4
|
-
private static readonly _sdkName;
|
|
5
|
-
private static readonly _sdkVersion;
|
|
6
|
-
private static readonly _defaultBaseUrl;
|
|
7
|
-
private static readonly _storageDeviceIdKey;
|
|
8
|
-
private static readonly _storageVisitorIdKey;
|
|
9
|
-
private _appKey;
|
|
10
|
-
private _baseUrl;
|
|
11
|
-
private _debug;
|
|
12
|
-
private _deviceId;
|
|
13
|
-
private _visitorId;
|
|
14
|
-
private constructor();
|
|
15
|
-
init(options: {
|
|
16
|
-
appKey: string;
|
|
17
|
-
baseUrl?: string;
|
|
18
|
-
debug?: boolean;
|
|
19
|
-
deviceId?: string;
|
|
20
|
-
}): Promise<void>;
|
|
21
|
-
track(eventKey: string, params?: Record<string, any>): Promise<void>;
|
|
22
|
-
private _loadOrCreateDeviceId;
|
|
23
|
-
private _loadOrCreateVisitorId;
|
|
24
|
-
private _getScreenInfo;
|
|
25
|
-
private _log;
|
|
26
|
-
}
|
package/dist/tracker.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.BytemTracker = void 0;
|
|
16
|
-
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
17
|
-
const react_native_1 = require("react-native");
|
|
18
|
-
const uuid_1 = require("./uuid");
|
|
19
|
-
class BytemTracker {
|
|
20
|
-
static get instance() {
|
|
21
|
-
if (!this._instance) {
|
|
22
|
-
this._instance = new BytemTracker();
|
|
23
|
-
}
|
|
24
|
-
return this._instance;
|
|
25
|
-
}
|
|
26
|
-
constructor() {
|
|
27
|
-
this._appKey = null;
|
|
28
|
-
this._baseUrl = BytemTracker._defaultBaseUrl;
|
|
29
|
-
this._debug = false;
|
|
30
|
-
this._deviceId = null;
|
|
31
|
-
this._visitorId = null;
|
|
32
|
-
}
|
|
33
|
-
init(options) {
|
|
34
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
-
var _a;
|
|
36
|
-
this._appKey = options.appKey;
|
|
37
|
-
this._debug = (_a = options.debug) !== null && _a !== void 0 ? _a : false;
|
|
38
|
-
if (options.baseUrl) {
|
|
39
|
-
this._baseUrl = options.baseUrl;
|
|
40
|
-
}
|
|
41
|
-
if (options.deviceId) {
|
|
42
|
-
this._deviceId = options.deviceId;
|
|
43
|
-
yield async_storage_1.default.setItem(BytemTracker._storageDeviceIdKey, this._deviceId);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
yield this._loadOrCreateDeviceId();
|
|
47
|
-
}
|
|
48
|
-
yield this._loadOrCreateVisitorId();
|
|
49
|
-
this._log('init', {
|
|
50
|
-
deviceId: this._deviceId,
|
|
51
|
-
visitorId: this._visitorId,
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
track(eventKey_1) {
|
|
56
|
-
return __awaiter(this, arguments, void 0, function* (eventKey, params = {}) {
|
|
57
|
-
if (!this._appKey || !this._deviceId || !this._visitorId) {
|
|
58
|
-
this._log('track skipped: not initialized');
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const payload = {
|
|
62
|
-
app_key: this._appKey,
|
|
63
|
-
device_id: this._deviceId,
|
|
64
|
-
visitor_id: this._visitorId,
|
|
65
|
-
event_key: eventKey,
|
|
66
|
-
params,
|
|
67
|
-
sdk: BytemTracker._sdkName,
|
|
68
|
-
sdk_version: BytemTracker._sdkVersion,
|
|
69
|
-
platform: react_native_1.Platform.OS,
|
|
70
|
-
timestamp: Date.now(),
|
|
71
|
-
screen: this._getScreenInfo(),
|
|
72
|
-
};
|
|
73
|
-
this._log('track', payload);
|
|
74
|
-
try {
|
|
75
|
-
yield fetch(`${this._baseUrl}/track`, {
|
|
76
|
-
method: 'POST',
|
|
77
|
-
headers: { 'Content-Type': 'application/json' },
|
|
78
|
-
body: JSON.stringify(payload),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch (e) {
|
|
82
|
-
this._log('track error', e);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
_loadOrCreateDeviceId() {
|
|
87
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
-
const stored = yield async_storage_1.default.getItem(BytemTracker._storageDeviceIdKey);
|
|
89
|
-
this._deviceId = stored !== null && stored !== void 0 ? stored : (0, uuid_1.generateUUID)();
|
|
90
|
-
if (!stored && this._deviceId) {
|
|
91
|
-
yield async_storage_1.default.setItem(BytemTracker._storageDeviceIdKey, this._deviceId);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
_loadOrCreateVisitorId() {
|
|
96
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
97
|
-
const stored = yield async_storage_1.default.getItem(BytemTracker._storageVisitorIdKey);
|
|
98
|
-
this._visitorId = stored !== null && stored !== void 0 ? stored : (0, uuid_1.generateUUID)();
|
|
99
|
-
if (!stored && this._visitorId) {
|
|
100
|
-
yield async_storage_1.default.setItem(BytemTracker._storageVisitorIdKey, this._visitorId);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
_getScreenInfo() {
|
|
105
|
-
const { width, height } = react_native_1.Dimensions.get('window');
|
|
106
|
-
return { width, height };
|
|
107
|
-
}
|
|
108
|
-
_log(...args) {
|
|
109
|
-
if (this._debug) {
|
|
110
|
-
console.log('[BytemTracker]', ...args);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
exports.BytemTracker = BytemTracker;
|
|
115
|
-
BytemTracker._instance = null;
|
|
116
|
-
BytemTracker._sdkName = 'bytemTrackerReact';
|
|
117
|
-
BytemTracker._sdkVersion = '0.0.1';
|
|
118
|
-
BytemTracker._defaultBaseUrl = 'https://tracking.server.bytecon.com';
|
|
119
|
-
BytemTracker._storageDeviceIdKey = 'bytem_fp';
|
|
120
|
-
BytemTracker._storageVisitorIdKey = 'bytem_visitor_id';
|
package/dist/types.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/** SDK 初始化配置 */
|
|
2
|
-
export interface BytemConfig {
|
|
3
|
-
appKey: string;
|
|
4
|
-
baseUrl?: string;
|
|
5
|
-
debug?: boolean;
|
|
6
|
-
visitorId?: string;
|
|
7
|
-
appScheme?: string;
|
|
8
|
-
}
|
|
9
|
-
/** 基础事件可选参数 */
|
|
10
|
-
export interface BytemEventOptions {
|
|
11
|
-
count?: number;
|
|
12
|
-
sum?: number;
|
|
13
|
-
duration?: number;
|
|
14
|
-
}
|
|
15
|
-
/** 用户资料参数 */
|
|
16
|
-
export interface BytemUserParams {
|
|
17
|
-
userId?: string;
|
|
18
|
-
name?: string;
|
|
19
|
-
email?: string;
|
|
20
|
-
phone?: string;
|
|
21
|
-
gender?: "M" | "F";
|
|
22
|
-
birthYear?: number;
|
|
23
|
-
custom?: Record<string, any>;
|
|
24
|
-
}
|
package/dist/uuid.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function generateUUID(): string;
|
package/dist/uuid.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateUUID = generateUUID;
|
|
4
|
-
const uuid_1 = require("uuid");
|
|
5
|
-
function jsUUIDv4() {
|
|
6
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
7
|
-
const r = (Math.random() * 16) | 0;
|
|
8
|
-
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
9
|
-
return v.toString(16);
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
function generateUUID() {
|
|
13
|
-
try {
|
|
14
|
-
return (0, uuid_1.v4)();
|
|
15
|
-
}
|
|
16
|
-
catch (_a) {
|
|
17
|
-
return jsUUIDv4();
|
|
18
|
-
}
|
|
19
|
-
}
|
|
File without changes
|