mimz-react-native-tracker 1.0.0
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 +246 -0
- package/bin/mimz-setup.js +275 -0
- package/package.json +47 -0
- package/src/index.d.ts +131 -0
- package/src/index.js +47 -0
- package/src/network.js +134 -0
- package/src/storage.js +117 -0
- package/src/tracker.js +647 -0
- package/src/utils.js +180 -0
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Mimz React Native Tracker SDK
|
|
2
|
+
|
|
3
|
+
Analytics tracking SDK for React Native apps — the native equivalent of the Mimz web tracking script.
|
|
4
|
+
|
|
5
|
+
Track **visitors, UTM parameters, conversions, contacts, and user engagement** in your native mobile app, exactly like the Mimz web script does on websites.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📦 Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install mimz-react-native-tracker @react-native-async-storage/async-storage
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For iOS, also run:
|
|
16
|
+
```bash
|
|
17
|
+
cd ios && npx pod-install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🚀 Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Initialize the Tracker
|
|
25
|
+
|
|
26
|
+
In your `App.js` or entry file:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
30
|
+
import MimzTracker from 'mimz-react-native-tracker';
|
|
31
|
+
|
|
32
|
+
// Initialize when the app starts
|
|
33
|
+
const initTracking = async () => {
|
|
34
|
+
await MimzTracker.init({
|
|
35
|
+
clientId: 'YOUR_MIMZ_CLIENT_ID', // From your Mimz dashboard (userId)
|
|
36
|
+
backendUrl: 'https://your-mimz-backend.com', // Your Mimz backend URL
|
|
37
|
+
asyncStorage: AsyncStorage,
|
|
38
|
+
appName: 'MyApp',
|
|
39
|
+
appVersion: '1.0.0',
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Call it in your App component
|
|
44
|
+
function App() {
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
initTracking();
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
return (/* your app */);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Track Deep Links (UTM Parameters)
|
|
54
|
+
|
|
55
|
+
When your app receives a deep link (e.g., from an ad or email campaign), pass the URL to the tracker. The SDK will automatically extract UTMs like `utm_source`, `utm_medium`, `utm_campaign`, `gclid`, `fbclid`, etc.
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
import { Linking } from 'react-native';
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
// Handle deep links
|
|
62
|
+
const handleDeepLink = (event) => {
|
|
63
|
+
MimzTracker.trackDeepLink(event.url, 'facebook'); // optional referrer
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
Linking.addEventListener('url', handleDeepLink);
|
|
67
|
+
|
|
68
|
+
// Handle initial URL (if app opened via deep link)
|
|
69
|
+
Linking.getInitialURL().then((url) => {
|
|
70
|
+
if (url) MimzTracker.trackDeepLink(url);
|
|
71
|
+
});
|
|
72
|
+
}, []);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Track Screen Views
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// In your navigation listener or screen components:
|
|
79
|
+
MimzTracker.trackScreenView('HomeScreen');
|
|
80
|
+
MimzTracker.trackScreenView('ProductDetail', { productId: '123', category: 'Shoes' });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 4. Track Custom Events
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
MimzTracker.trackEvent('add_to_cart', { productId: '123', price: 29.99 });
|
|
87
|
+
MimzTracker.trackEvent('button_click', { buttonId: 'signup_btn', text: 'Sign Up' });
|
|
88
|
+
MimzTracker.trackEvent('search', { query: 'running shoes' });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 5. Track Conversions
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
MimzTracker.trackConversion({
|
|
95
|
+
productName: 'Premium Plan',
|
|
96
|
+
value: 49.99,
|
|
97
|
+
currency: 'USD',
|
|
98
|
+
conversionKey: 'premium_signup',
|
|
99
|
+
level: 'Purchase',
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 6. Track Contacts (Form Submissions)
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// When a user submits a form in the app
|
|
107
|
+
MimzTracker.trackContact({
|
|
108
|
+
email: 'user@example.com',
|
|
109
|
+
name: 'John Doe',
|
|
110
|
+
phone: '+1234567890',
|
|
111
|
+
source: 'Signup Form',
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 7. Identify Users
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// When a user logs in or provides their info
|
|
119
|
+
MimzTracker.identify({
|
|
120
|
+
email: 'user@example.com',
|
|
121
|
+
name: 'John',
|
|
122
|
+
phone: '+1234567890',
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 8. Flush on App Background
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
import { AppState } from 'react-native';
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const subscription = AppState.addEventListener('change', (nextAppState) => {
|
|
133
|
+
if (nextAppState === 'background' || nextAppState === 'inactive') {
|
|
134
|
+
MimzTracker.flush(); // Send pending data immediately
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return () => subscription.remove();
|
|
138
|
+
}, []);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 9. Reset on Logout
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
const handleLogout = async () => {
|
|
145
|
+
await MimzTracker.reset(); // Clears all stored tracking data
|
|
146
|
+
// ... your logout logic
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 🔄 Web Script ↔ Native SDK Feature Mapping
|
|
153
|
+
|
|
154
|
+
| Web Script Feature | Native SDK Equivalent | How It Works |
|
|
155
|
+
| ------------------------------ | ---------------------------------- | ------------------------------------- |
|
|
156
|
+
| `visitorId` (cookie) | `MimzTracker.getVisitorId()` | UUID stored in AsyncStorage |
|
|
157
|
+
| `visitId` (cookie) | `MimzTracker.getVisitId()` | Timestamp-based, new per session |
|
|
158
|
+
| UTM params (URL query string) | `MimzTracker.trackDeepLink(url)` | Parsed from deep link URLs |
|
|
159
|
+
| First-touch UTMs (cookie) | Automatic | Stored on first deep link |
|
|
160
|
+
| Page view tracking | `MimzTracker.trackScreenView()` | Tracks screen names instead of URLs |
|
|
161
|
+
| Click/scroll tracking | `MimzTracker.trackEvent()` | Manual event tracking |
|
|
162
|
+
| Form submission tracking | `MimzTracker.trackContact()` | Creates Contact + Conversion |
|
|
163
|
+
| Conversion tracking | `MimzTracker.trackConversion()` | Same payload as web |
|
|
164
|
+
| Device info (userAgent) | Automatic | Uses React Native Platform API |
|
|
165
|
+
| IP geolocation | Automatic | Uses ipify.org |
|
|
166
|
+
| Traffic source detection | Via deep link referrer | Same classification logic as web |
|
|
167
|
+
| Session duration | `MimzTracker.getSessionDuration()` | Calculated from init time |
|
|
168
|
+
| Session count | Automatic | Incremented per init() |
|
|
169
|
+
| `window.userId` (cookie) | `config.clientId` | Passed during init() |
|
|
170
|
+
| SPA navigation tracking | `MimzTracker.trackScreenView()` | Call on each screen navigation |
|
|
171
|
+
| `postUserDataToServer()` | Automatic | Same `/api/visitors/add-visitors` API |
|
|
172
|
+
| `beforeunload` flush | `MimzTracker.flush()` + AppState | Call on app background |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 📱 React Navigation Integration
|
|
177
|
+
|
|
178
|
+
If you use React Navigation, you can automatically track screen views:
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
import { NavigationContainer } from '@react-navigation/native';
|
|
182
|
+
|
|
183
|
+
function App() {
|
|
184
|
+
const routeNameRef = useRef();
|
|
185
|
+
const navigationRef = useRef();
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<NavigationContainer
|
|
189
|
+
ref={navigationRef}
|
|
190
|
+
onReady={() => {
|
|
191
|
+
routeNameRef.current = navigationRef.current.getCurrentRoute().name;
|
|
192
|
+
}}
|
|
193
|
+
onStateChange={() => {
|
|
194
|
+
const currentRouteName = navigationRef.current.getCurrentRoute().name;
|
|
195
|
+
const previousRouteName = routeNameRef.current;
|
|
196
|
+
|
|
197
|
+
if (previousRouteName !== currentRouteName) {
|
|
198
|
+
MimzTracker.trackScreenView(currentRouteName);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
routeNameRef.current = currentRouteName;
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
{/* screens */}
|
|
205
|
+
</NavigationContainer>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 🔑 API Reference
|
|
213
|
+
|
|
214
|
+
| Method | Description |
|
|
215
|
+
| ------------------------- | ----------------------------------------------------- |
|
|
216
|
+
| `init(config)` | Initialize the SDK (must be called first) |
|
|
217
|
+
| `trackDeepLink(url)` | Track a deep link and extract UTM params |
|
|
218
|
+
| `trackScreenView(name)` | Track a screen view |
|
|
219
|
+
| `trackEvent(name, props)` | Track a custom event |
|
|
220
|
+
| `trackConversion(data)` | Track a conversion |
|
|
221
|
+
| `trackContact(data)` | Track a contact (form submission) |
|
|
222
|
+
| `identify(data)` | Set user identity (email, name, phone) |
|
|
223
|
+
| `setUtmParams(utms)` | Manually set UTM parameters |
|
|
224
|
+
| `getVisitorId()` | Get the current visitor ID |
|
|
225
|
+
| `getVisitId()` | Get the current visit/session ID |
|
|
226
|
+
| `getSessionDuration()` | Get session duration in seconds |
|
|
227
|
+
| `flush()` | Send pending data immediately |
|
|
228
|
+
| `reset()` | Clear all stored data (for logout) |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 📋 Backend API Compatibility
|
|
233
|
+
|
|
234
|
+
This SDK sends data to the exact same backend endpoints as the web script:
|
|
235
|
+
|
|
236
|
+
- `POST /api/visitors/add-visitors` — Visitor tracking
|
|
237
|
+
- `POST /api/visitors/email-submission` — Contact + Conversion
|
|
238
|
+
- `POST /api/conversion/add-conversion` — Standalone conversions
|
|
239
|
+
|
|
240
|
+
The payload format matches the web script exactly, so your existing Mimz dashboard will display native app data seamlessly alongside web data.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mimz React Native Tracker — Setup CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx mimz-setup <clientId>
|
|
8
|
+
* npx mimz-setup <clientId> <backendUrl>
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* npx mimz-setup 68b58625949a81fd08330b38
|
|
12
|
+
* npx mimz-setup 68b58625949a81fd08330b38 https://mimz-backend.onrender.com
|
|
13
|
+
*
|
|
14
|
+
* This script will automatically inject tracking initialization code into your App.js
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
// ── Parse CLI arguments ───────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
|
|
24
|
+
// Handle --help
|
|
25
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log('Usage:');
|
|
28
|
+
console.log(' npx mimz-setup <clientId>');
|
|
29
|
+
console.log(' npx mimz-setup <clientId> <backendUrl>');
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log('Arguments:');
|
|
32
|
+
console.log(' clientId Your Mimz Client ID (userId from the Mimz dashboard)');
|
|
33
|
+
console.log(' backendUrl Your Mimz Backend URL (default: https://mimz-backend.onrender.com)');
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log('Examples:');
|
|
36
|
+
console.log(' npx mimz-setup 68b58625949a81fd08330b38');
|
|
37
|
+
console.log(' npx mimz-setup 68b58625949a81fd08330b38 https://my-backend.com');
|
|
38
|
+
console.log('');
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const clientId = args[0];
|
|
43
|
+
let backendUrl = args[1] || 'https://mimz-backend.onrender.com';
|
|
44
|
+
|
|
45
|
+
// Validate
|
|
46
|
+
if (!clientId) {
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log('╔══════════════════════════════════════════════╗');
|
|
49
|
+
console.log('║ 🚀 Mimz React Native Tracker Setup ║');
|
|
50
|
+
console.log('╚══════════════════════════════════════════════╝');
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log('❌ Client ID is required!');
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log('Usage:');
|
|
55
|
+
console.log(' npx mimz-setup <your-client-id>');
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log('Example:');
|
|
58
|
+
console.log(' npx mimz-setup 68b58625949a81fd08330b38');
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log('You can find your Client ID in the Mimz dashboard.');
|
|
61
|
+
console.log('');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Normalize: remove trailing slash
|
|
66
|
+
backendUrl = backendUrl.replace(/\/+$/, '');
|
|
67
|
+
|
|
68
|
+
// ── The tracking code template ────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
const getTrackingImports = () => {
|
|
71
|
+
return `import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
72
|
+
import MimzTracker from 'mimz-react-native-tracker';`;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const getTrackingInitCode = (cId, bUrl, appName) => {
|
|
76
|
+
return `
|
|
77
|
+
// ── Mimz Tracking ──────────────────────────────────────────────────────
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const initMimzTracking = async () => {
|
|
80
|
+
try {
|
|
81
|
+
await MimzTracker.init({
|
|
82
|
+
clientId: '${cId}',
|
|
83
|
+
backendUrl: '${bUrl}',
|
|
84
|
+
asyncStorage: AsyncStorage,
|
|
85
|
+
appName: '${appName}',
|
|
86
|
+
appVersion: '1.0.0',
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn('Mimz tracking init failed:', error.message);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
initMimzTracking();
|
|
93
|
+
|
|
94
|
+
// Flush tracking data when app goes to background
|
|
95
|
+
const subscription = require('react-native').AppState.addEventListener('change', (state) => {
|
|
96
|
+
if (state === 'background' || state === 'inactive') {
|
|
97
|
+
MimzTracker.flush();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return () => subscription.remove();
|
|
101
|
+
}, []);
|
|
102
|
+
// ── End Mimz Tracking ──────────────────────────────────────────────────`;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ── Main setup logic ──────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log('╔══════════════════════════════════════════════╗');
|
|
109
|
+
console.log('║ 🚀 Mimz React Native Tracker Setup ║');
|
|
110
|
+
console.log('╚══════════════════════════════════════════════╝');
|
|
111
|
+
console.log('');
|
|
112
|
+
|
|
113
|
+
// Detect the app name from package.json
|
|
114
|
+
let appName = 'MyApp';
|
|
115
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
116
|
+
if (fs.existsSync(pkgPath)) {
|
|
117
|
+
try {
|
|
118
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
119
|
+
appName = pkg.name || 'MyApp';
|
|
120
|
+
} catch (e) {
|
|
121
|
+
// ignore
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(`✅ Client ID: ${clientId}`);
|
|
126
|
+
console.log(`✅ Backend URL: ${backendUrl}`);
|
|
127
|
+
console.log(`✅ App Name: ${appName}`);
|
|
128
|
+
console.log('');
|
|
129
|
+
|
|
130
|
+
// Find App.js
|
|
131
|
+
const appJsPaths = [
|
|
132
|
+
path.join(process.cwd(), 'App.js'),
|
|
133
|
+
path.join(process.cwd(), 'App.jsx'),
|
|
134
|
+
path.join(process.cwd(), 'App.tsx'),
|
|
135
|
+
path.join(process.cwd(), 'src', 'App.js'),
|
|
136
|
+
path.join(process.cwd(), 'src', 'App.jsx'),
|
|
137
|
+
path.join(process.cwd(), 'src', 'App.tsx'),
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
let appJsPath = null;
|
|
141
|
+
for (const p of appJsPaths) {
|
|
142
|
+
if (fs.existsSync(p)) {
|
|
143
|
+
appJsPath = p;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!appJsPath) {
|
|
149
|
+
console.log('⚠️ Could not find App.js in your project.');
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log('Add this manually to your App.js:');
|
|
152
|
+
console.log('──────────────────────────────────────────────');
|
|
153
|
+
console.log(getTrackingImports());
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log('// Inside your App component, add this useEffect:');
|
|
156
|
+
console.log(getTrackingInitCode(clientId, backendUrl, appName));
|
|
157
|
+
console.log('');
|
|
158
|
+
process.exit(0);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`📄 Found: ${path.relative(process.cwd(), appJsPath)}`);
|
|
162
|
+
|
|
163
|
+
// Read the file
|
|
164
|
+
let content = fs.readFileSync(appJsPath, 'utf8');
|
|
165
|
+
|
|
166
|
+
// Check if already set up
|
|
167
|
+
if (content.includes('mimz-react-native-tracker') || content.includes('MimzTracker')) {
|
|
168
|
+
// Remove existing Mimz code to replace with new
|
|
169
|
+
content = content.replace(/import MimzTracker from ['"]mimz-react-native-tracker['"];?\r?\n?/g, '');
|
|
170
|
+
content = content.replace(/import AsyncStorage from ['"]@react-native-async-storage\/async-storage['"];?\r?\n?/g, '');
|
|
171
|
+
// Remove the tracking useEffect block
|
|
172
|
+
content = content.replace(/\s*\/\/ ── Mimz Tracking[\s\S]*?\/\/ ── End Mimz Tracking ──+/g, '');
|
|
173
|
+
console.log('🔄 Replacing existing Mimz tracking code...');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── Inject imports at the top ──────────────────────────────────────────────
|
|
177
|
+
const trackingImports = getTrackingImports();
|
|
178
|
+
|
|
179
|
+
// Find the last import line and add our imports after it
|
|
180
|
+
const importRegex = /^(import\s+.+?;?\s*$)/gm;
|
|
181
|
+
let lastImportIndex = -1;
|
|
182
|
+
let match;
|
|
183
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
184
|
+
lastImportIndex = match.index + match[0].length;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (lastImportIndex > -1) {
|
|
188
|
+
content = content.slice(0, lastImportIndex) + '\n' + trackingImports + content.slice(lastImportIndex);
|
|
189
|
+
} else {
|
|
190
|
+
// No imports found — try require() style
|
|
191
|
+
const requireRegex = /^(const\s+.+?=\s*require\(.+?\);?\s*$)/gm;
|
|
192
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
193
|
+
lastImportIndex = match.index + match[0].length;
|
|
194
|
+
}
|
|
195
|
+
if (lastImportIndex > -1) {
|
|
196
|
+
content = content.slice(0, lastImportIndex) + '\n' + trackingImports + content.slice(lastImportIndex);
|
|
197
|
+
} else {
|
|
198
|
+
content = trackingImports + '\n' + content;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Inject useEffect inside the App component ──────────────────────────────
|
|
203
|
+
const trackingInit = getTrackingInitCode(clientId, backendUrl, appName);
|
|
204
|
+
|
|
205
|
+
// Find the pattern: "return (" or "return(<" in the main component
|
|
206
|
+
const returnRegex = /(\r?\n)([ \t]*)(return\s*\()/;
|
|
207
|
+
const returnMatch = returnRegex.exec(content);
|
|
208
|
+
|
|
209
|
+
if (returnMatch) {
|
|
210
|
+
const insertPos = returnMatch.index;
|
|
211
|
+
const indent = returnMatch[2] || ' ';
|
|
212
|
+
// Add the useEffect block before the return statement
|
|
213
|
+
const indentedCode = trackingInit
|
|
214
|
+
.split('\n')
|
|
215
|
+
.map((line) => (line.trim() ? indent + line.trimStart() : ''))
|
|
216
|
+
.join('\n');
|
|
217
|
+
content = content.slice(0, insertPos) + '\n' + indentedCode + '\n' + content.slice(insertPos);
|
|
218
|
+
} else {
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log('⚠️ Could not find the right place to inject tracking code.');
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log('Add this useEffect inside your App component manually:');
|
|
223
|
+
console.log('──────────────────────────────────────────────');
|
|
224
|
+
console.log(trackingInit);
|
|
225
|
+
console.log('');
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── Ensure useEffect is imported ───────────────────────────────────────────
|
|
230
|
+
if (!content.includes('useEffect')) {
|
|
231
|
+
// Try to add useEffect to existing React import
|
|
232
|
+
const reactImportFixed = content.replace(
|
|
233
|
+
/import\s+React\s*,?\s*\{([^}]*)\}\s*from\s*['"]react['"]/,
|
|
234
|
+
(m, imports) => {
|
|
235
|
+
if (!imports.includes('useEffect')) {
|
|
236
|
+
return m.replace(imports, `${imports.trim()}, useEffect`);
|
|
237
|
+
}
|
|
238
|
+
return m;
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (reactImportFixed !== content) {
|
|
243
|
+
content = reactImportFixed;
|
|
244
|
+
} else if (!content.includes('useEffect')) {
|
|
245
|
+
// Try plain React import
|
|
246
|
+
content = content.replace(
|
|
247
|
+
/import\s+React\s+from\s*['"]react['"]/,
|
|
248
|
+
"import React, { useEffect } from 'react'"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ── Write the updated file ─────────────────────────────────────────────────
|
|
254
|
+
// Create a backup
|
|
255
|
+
const backupPath = appJsPath + '.bak';
|
|
256
|
+
fs.writeFileSync(backupPath, fs.readFileSync(appJsPath, 'utf8'));
|
|
257
|
+
|
|
258
|
+
// Write the patched file
|
|
259
|
+
fs.writeFileSync(appJsPath, content, 'utf8');
|
|
260
|
+
|
|
261
|
+
console.log('');
|
|
262
|
+
console.log('╔══════════════════════════════════════════════╗');
|
|
263
|
+
console.log('║ ✅ Setup Complete! ║');
|
|
264
|
+
console.log('╚══════════════════════════════════════════════╝');
|
|
265
|
+
console.log('');
|
|
266
|
+
console.log(`📄 Updated: ${path.relative(process.cwd(), appJsPath)}`);
|
|
267
|
+
console.log(`📦 Backup: ${path.relative(process.cwd(), backupPath)}`);
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log('📦 Make sure you have installed:');
|
|
270
|
+
console.log(' npm install @react-native-async-storage/async-storage');
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(' For iOS: cd ios && npx pod-install');
|
|
273
|
+
console.log('');
|
|
274
|
+
console.log('🚀 You\'re all set! Your app will now send tracking data to Mimz.');
|
|
275
|
+
console.log('');
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mimz-react-native-tracker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Mimz Analytics Tracking SDK for React Native apps. Track visitors, UTM parameters, conversions, contacts, and user engagement in native mobile apps.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mimz-setup": "./bin/mimz-setup.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"setup": "node bin/mimz-setup.js",
|
|
12
|
+
"test": "echo \"No tests yet\" && exit 0",
|
|
13
|
+
"lint": "eslint src/",
|
|
14
|
+
"postinstall": "echo \"\\n🚀 Run 'npx mimz-setup' to automatically configure tracking in your App.js\\n\""
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mimz",
|
|
18
|
+
"analytics",
|
|
19
|
+
"tracking",
|
|
20
|
+
"react-native",
|
|
21
|
+
"utm",
|
|
22
|
+
"visitor",
|
|
23
|
+
"conversion",
|
|
24
|
+
"contact",
|
|
25
|
+
"mobile-analytics"
|
|
26
|
+
],
|
|
27
|
+
"author": "Mimz",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
31
|
+
"react": ">=16.8.0",
|
|
32
|
+
"react-native": ">=0.60.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"@react-native-async-storage/async-storage": {
|
|
36
|
+
"optional": false
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src/",
|
|
41
|
+
"bin/"
|
|
42
|
+
],
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": ""
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript type definitions for mimz-react-native-tracker
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface MimzConfig {
|
|
6
|
+
/** Your Mimz client/account ID from the dashboard */
|
|
7
|
+
clientId: string;
|
|
8
|
+
/** The Mimz backend URL (e.g., https://mimz-backend.onrender.com) */
|
|
9
|
+
backendUrl: string;
|
|
10
|
+
/** The AsyncStorage module from @react-native-async-storage/async-storage */
|
|
11
|
+
asyncStorage: any;
|
|
12
|
+
/** Your app's name (for tracking context) */
|
|
13
|
+
appName?: string;
|
|
14
|
+
/** Your app's version */
|
|
15
|
+
appVersion?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ConversionData {
|
|
19
|
+
/** Product or conversion name */
|
|
20
|
+
productName?: string;
|
|
21
|
+
/** Conversion value */
|
|
22
|
+
value?: number;
|
|
23
|
+
/** Currency code (default: 'USD') */
|
|
24
|
+
currency?: string;
|
|
25
|
+
/** Unique conversion identifier */
|
|
26
|
+
conversionKey?: string;
|
|
27
|
+
/** Conversion level/type */
|
|
28
|
+
level?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ContactData {
|
|
32
|
+
/** Email address */
|
|
33
|
+
email?: string;
|
|
34
|
+
/** Full name */
|
|
35
|
+
name?: string;
|
|
36
|
+
/** First name */
|
|
37
|
+
firstName?: string;
|
|
38
|
+
/** Last name */
|
|
39
|
+
lastName?: string;
|
|
40
|
+
/** Phone number */
|
|
41
|
+
phone?: string;
|
|
42
|
+
/** Where the contact was captured (e.g., 'Signup Form') */
|
|
43
|
+
source?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UserData {
|
|
47
|
+
/** Email */
|
|
48
|
+
email?: string;
|
|
49
|
+
/** Name */
|
|
50
|
+
name?: string;
|
|
51
|
+
/** Phone */
|
|
52
|
+
phone?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UtmParams {
|
|
56
|
+
utm_source?: string;
|
|
57
|
+
utm_medium?: string;
|
|
58
|
+
utm_campaign?: string;
|
|
59
|
+
utm_term?: string;
|
|
60
|
+
utm_content?: string;
|
|
61
|
+
utm_device?: string;
|
|
62
|
+
utm_devicemodel?: string;
|
|
63
|
+
fbclid?: string;
|
|
64
|
+
msclkid?: string;
|
|
65
|
+
gclid?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Initialize the Mimz Tracker SDK
|
|
70
|
+
*/
|
|
71
|
+
export function init(config: MimzConfig): Promise<void>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Track a deep link URL — extracts and stores UTM parameters
|
|
75
|
+
*/
|
|
76
|
+
export function trackDeepLink(url: string, referrer?: string): Promise<void>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Track a screen view (replaces web page view tracking)
|
|
80
|
+
*/
|
|
81
|
+
export function trackScreenView(screenName: string, properties?: Record<string, any>): Promise<void>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Track a custom event
|
|
85
|
+
*/
|
|
86
|
+
export function trackEvent(eventName: string, properties?: Record<string, any>): Promise<void>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Track a conversion
|
|
90
|
+
*/
|
|
91
|
+
export function trackConversion(conversionData?: ConversionData): Promise<any>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Track a contact (form submission equivalent)
|
|
95
|
+
*/
|
|
96
|
+
export function trackContact(contactData?: ContactData): Promise<any>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Identify a user by setting their personal details
|
|
100
|
+
*/
|
|
101
|
+
export function identify(userData?: UserData): Promise<void>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Manually set UTM parameters
|
|
105
|
+
*/
|
|
106
|
+
export function setUtmParams(utms?: UtmParams): Promise<void>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the current visitor ID
|
|
110
|
+
*/
|
|
111
|
+
export function getVisitorId(): string | null;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the current visit ID
|
|
115
|
+
*/
|
|
116
|
+
export function getVisitId(): number | null;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get current session duration in seconds
|
|
120
|
+
*/
|
|
121
|
+
export function getSessionDuration(): number;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Flush: send current interactions to backend immediately
|
|
125
|
+
*/
|
|
126
|
+
export function flush(): Promise<void>;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Reset the tracker (clear all stored data)
|
|
130
|
+
*/
|
|
131
|
+
export function reset(): Promise<void>;
|