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/src/tracker.js
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MimzTracker - Core tracking engine for React Native
|
|
3
|
+
*
|
|
4
|
+
* This is the React Native equivalent of the web tracking script (scriptUtils.js).
|
|
5
|
+
* It replaces all browser-dependent code (cookies, DOM, window, document)
|
|
6
|
+
* with React Native compatible APIs (AsyncStorage, fetch, Platform).
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Visitor ID generation & persistence
|
|
10
|
+
* - Visit ID (session) tracking
|
|
11
|
+
* - UTM parameter tracking (from deep links)
|
|
12
|
+
* - First-touch & last-touch attribution
|
|
13
|
+
* - Traffic source detection
|
|
14
|
+
* - Device info collection
|
|
15
|
+
* - IP-based geolocation
|
|
16
|
+
* - Conversion tracking
|
|
17
|
+
* - Contact capture
|
|
18
|
+
* - Screen view tracking (replaces page view tracking)
|
|
19
|
+
* - User engagement tracking
|
|
20
|
+
* - Session duration tracking
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const storage = require('./storage');
|
|
24
|
+
const network = require('./network');
|
|
25
|
+
const {
|
|
26
|
+
generateUniqueId,
|
|
27
|
+
parseUtmFromUrl,
|
|
28
|
+
getDeviceInfo,
|
|
29
|
+
getTrafficSource,
|
|
30
|
+
getUserIp,
|
|
31
|
+
} = require('./utils');
|
|
32
|
+
|
|
33
|
+
// ─── Internal State ──────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
let isInitialized = false;
|
|
36
|
+
let clientId = null; // The Mimz user/account ID (replaces window.userId)
|
|
37
|
+
let visitorId = null;
|
|
38
|
+
let visitId = null;
|
|
39
|
+
let sessionStartTime = null;
|
|
40
|
+
let sessionCount = 0;
|
|
41
|
+
let currentScreen = null;
|
|
42
|
+
let firstSeenTimestamp = null;
|
|
43
|
+
let cachedIp = null;
|
|
44
|
+
let cachedDeviceInfo = null;
|
|
45
|
+
let currentUtmParams = {};
|
|
46
|
+
let firstTouchUtmParams = {};
|
|
47
|
+
let firstTrafficSource = null;
|
|
48
|
+
let interactions = [];
|
|
49
|
+
let appName = null;
|
|
50
|
+
let appVersion = null;
|
|
51
|
+
|
|
52
|
+
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
const API_ENDPOINTS = {
|
|
55
|
+
ADD_VISITORS: '/api/visitors/add-visitors',
|
|
56
|
+
ADD_VISITOR_DATA: '/api/visitors/visitor-data',
|
|
57
|
+
ADD_CONVERSION: '/api/conversion/add-conversion',
|
|
58
|
+
EMAIL_SUBMISSION: '/api/visitors/email-submission',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Initialize the Mimz Tracker SDK
|
|
65
|
+
*
|
|
66
|
+
* @param {object} config - Configuration object
|
|
67
|
+
* @param {string} config.clientId - Your Mimz client/account ID (REQUIRED - this is the userId from your Mimz dashboard)
|
|
68
|
+
* @param {string} config.backendUrl - The Mimz backend URL (REQUIRED - e.g., https://mimz-backend.onrender.com)
|
|
69
|
+
* @param {object} config.asyncStorage - The AsyncStorage module (REQUIRED - from @react-native-async-storage/async-storage)
|
|
70
|
+
* @param {string} [config.appName] - Your app's name (for tracking context)
|
|
71
|
+
* @param {string} [config.appVersion] - Your app's version (for tracking context)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
75
|
+
* import MimzTracker from 'mimz-react-native-tracker';
|
|
76
|
+
*
|
|
77
|
+
* await MimzTracker.init({
|
|
78
|
+
* clientId: '68b58625949a81fd08330b38',
|
|
79
|
+
* backendUrl: 'https://mimz-backend.onrender.com',
|
|
80
|
+
* asyncStorage: AsyncStorage,
|
|
81
|
+
* appName: 'MyApp',
|
|
82
|
+
* appVersion: '1.0.0',
|
|
83
|
+
* });
|
|
84
|
+
*/
|
|
85
|
+
const init = async (config = {}) => {
|
|
86
|
+
if (isInitialized) {
|
|
87
|
+
console.warn('[MimzTracker] Already initialized. Call reset() first to re-initialize.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { clientId: cId, backendUrl, asyncStorage, appName: an, appVersion: av } = config;
|
|
92
|
+
|
|
93
|
+
// Validate required fields
|
|
94
|
+
if (!cId) {
|
|
95
|
+
console.error('[MimzTracker] clientId is required. Get it from your Mimz dashboard.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!backendUrl) {
|
|
99
|
+
console.error('[MimzTracker] backendUrl is required (e.g., https://mimz-backend.onrender.com).');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!asyncStorage) {
|
|
103
|
+
console.error('[MimzTracker] asyncStorage is required. Pass the AsyncStorage module.');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Initialize sub-modules
|
|
108
|
+
storage.init(asyncStorage);
|
|
109
|
+
network.init(backendUrl);
|
|
110
|
+
|
|
111
|
+
clientId = cId;
|
|
112
|
+
appName = an || null;
|
|
113
|
+
appVersion = av || null;
|
|
114
|
+
sessionStartTime = Date.now();
|
|
115
|
+
|
|
116
|
+
// ── Restore or create visitor ID ────────────────────────────────────────
|
|
117
|
+
const storedVisitorId = await storage.get('visitorId');
|
|
118
|
+
if (storedVisitorId) {
|
|
119
|
+
visitorId = storedVisitorId;
|
|
120
|
+
} else {
|
|
121
|
+
visitorId = generateUniqueId();
|
|
122
|
+
await storage.set('visitorId', visitorId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Create a new visit ID for this session ────────────────────────────
|
|
126
|
+
visitId = Date.now();
|
|
127
|
+
await storage.set('visitId', String(visitId));
|
|
128
|
+
|
|
129
|
+
// ── Session count ─────────────────────────────────────────────────────
|
|
130
|
+
const storedSessionCount = await storage.get('sessionCount');
|
|
131
|
+
sessionCount = storedSessionCount ? parseInt(storedSessionCount, 10) + 1 : 1;
|
|
132
|
+
await storage.set('sessionCount', String(sessionCount));
|
|
133
|
+
|
|
134
|
+
// ── First seen timestamp ──────────────────────────────────────────────
|
|
135
|
+
const storedFirstSeen = await storage.get('firstSeen');
|
|
136
|
+
if (storedFirstSeen) {
|
|
137
|
+
firstSeenTimestamp = storedFirstSeen;
|
|
138
|
+
} else {
|
|
139
|
+
firstSeenTimestamp = new Date().toISOString();
|
|
140
|
+
await storage.set('firstSeen', firstSeenTimestamp);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Restore first-touch UTM ───────────────────────────────────────────
|
|
144
|
+
const storedFirstTouchUtm = await storage.getJSON('firstTouchUtm');
|
|
145
|
+
if (storedFirstTouchUtm) {
|
|
146
|
+
firstTouchUtmParams = storedFirstTouchUtm;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Restore first traffic source ──────────────────────────────────────
|
|
150
|
+
const storedFirstTrafficSource = await storage.get('firstTrafficSource');
|
|
151
|
+
if (storedFirstTrafficSource) {
|
|
152
|
+
firstTrafficSource = storedFirstTrafficSource;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Get device info (cached) ──────────────────────────────────────────
|
|
156
|
+
cachedDeviceInfo = getDeviceInfo();
|
|
157
|
+
|
|
158
|
+
// ── Get IP (non-blocking) ─────────────────────────────────────────────
|
|
159
|
+
getUserIp().then((ip) => {
|
|
160
|
+
cachedIp = ip;
|
|
161
|
+
storage.set('cachedIp', ip);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Fallback: use cached IP from storage
|
|
165
|
+
const storedIp = await storage.get('cachedIp');
|
|
166
|
+
if (storedIp) {
|
|
167
|
+
cachedIp = storedIp;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
isInitialized = true;
|
|
171
|
+
|
|
172
|
+
// ── Send initial visit data to the backend ────────────────────────────
|
|
173
|
+
const trackingData = await _buildTrackingPayload();
|
|
174
|
+
await _postVisitorData(trackingData);
|
|
175
|
+
|
|
176
|
+
console.log('[MimzTracker] ✅ Initialized successfully');
|
|
177
|
+
console.log(`[MimzTracker] visitorId: ${visitorId}`);
|
|
178
|
+
console.log(`[MimzTracker] visitId: ${visitId}`);
|
|
179
|
+
console.log(`[MimzTracker] sessionCount: ${sessionCount}`);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Track a deep link URL — extracts and stores UTM parameters
|
|
184
|
+
* Call this whenever your app receives a deep link / universal link
|
|
185
|
+
*
|
|
186
|
+
* @param {string} url - The deep link URL
|
|
187
|
+
* @param {string} [referrer] - Optional referrer URL (where the user came from)
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* // In your deep link handler:
|
|
191
|
+
* MimzTracker.trackDeepLink('myapp://open?utm_source=facebook&utm_campaign=summer_sale');
|
|
192
|
+
*/
|
|
193
|
+
const trackDeepLink = async (url, referrer = null) => {
|
|
194
|
+
_ensureInitialized();
|
|
195
|
+
|
|
196
|
+
// Parse UTMs from the deep link
|
|
197
|
+
const utms = parseUtmFromUrl(url);
|
|
198
|
+
if (Object.keys(utms).length > 0) {
|
|
199
|
+
currentUtmParams = { ...currentUtmParams, ...utms };
|
|
200
|
+
await storage.setJSON('currentUtm', currentUtmParams);
|
|
201
|
+
|
|
202
|
+
// Store first-touch UTMs if not already set
|
|
203
|
+
if (Object.keys(firstTouchUtmParams).length === 0) {
|
|
204
|
+
firstTouchUtmParams = { ...utms };
|
|
205
|
+
await storage.setJSON('firstTouchUtm', firstTouchUtmParams);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log('[MimzTracker] 🔗 Deep link UTMs captured:', utms);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Detect traffic source from the URL or referrer
|
|
212
|
+
if (referrer || url) {
|
|
213
|
+
// Check for paid indicators
|
|
214
|
+
let trafficSource = 'Direct';
|
|
215
|
+
if (utms.gclid || utms.msclkid || utms.fbclid) {
|
|
216
|
+
trafficSource = 'Paid';
|
|
217
|
+
} else {
|
|
218
|
+
const traffic = getTrafficSource(referrer || url);
|
|
219
|
+
trafficSource = traffic.trafficSource;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Store first traffic source
|
|
223
|
+
if (!firstTrafficSource) {
|
|
224
|
+
firstTrafficSource = trafficSource;
|
|
225
|
+
await storage.set('firstTrafficSource', firstTrafficSource);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Add to interactions
|
|
230
|
+
_addInteraction('deep_link', {
|
|
231
|
+
url,
|
|
232
|
+
referrer,
|
|
233
|
+
utms,
|
|
234
|
+
timestamp: new Date().toISOString(),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Re-post visitor data with updated UTMs
|
|
238
|
+
const trackingData = await _buildTrackingPayload();
|
|
239
|
+
trackingData.urlpt_url = url;
|
|
240
|
+
await _postVisitorData(trackingData);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Track a screen view (replaces web page view tracking)
|
|
245
|
+
*
|
|
246
|
+
* @param {string} screenName - The name/route of the current screen
|
|
247
|
+
* @param {object} [properties] - Additional properties to track
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* MimzTracker.trackScreenView('HomeScreen');
|
|
251
|
+
* MimzTracker.trackScreenView('ProductDetail', { productId: '123', category: 'Shoes' });
|
|
252
|
+
*/
|
|
253
|
+
const trackScreenView = async (screenName, properties = {}) => {
|
|
254
|
+
_ensureInitialized();
|
|
255
|
+
|
|
256
|
+
const previousScreen = currentScreen;
|
|
257
|
+
currentScreen = screenName;
|
|
258
|
+
|
|
259
|
+
_addInteraction('screen_view', {
|
|
260
|
+
screenName,
|
|
261
|
+
previousScreen,
|
|
262
|
+
properties,
|
|
263
|
+
timestamp: new Date().toISOString(),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Update visitor data with current screen info
|
|
267
|
+
const trackingData = await _buildTrackingPayload();
|
|
268
|
+
trackingData.urlpt_url = `app://${screenName}`;
|
|
269
|
+
trackingData.urlpt_url_base = screenName;
|
|
270
|
+
await _postVisitorData(trackingData);
|
|
271
|
+
|
|
272
|
+
console.log(`[MimzTracker] 📱 Screen view: ${screenName}`);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Track a custom event (replaces click/scroll/form tracking)
|
|
277
|
+
*
|
|
278
|
+
* @param {string} eventName - The event name (e.g., 'button_click', 'add_to_cart')
|
|
279
|
+
* @param {object} [properties] - Event properties
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* MimzTracker.trackEvent('add_to_cart', { productId: '123', price: 29.99 });
|
|
283
|
+
* MimzTracker.trackEvent('button_click', { buttonId: 'signup_btn', text: 'Sign Up' });
|
|
284
|
+
*/
|
|
285
|
+
const trackEvent = async (eventName, properties = {}) => {
|
|
286
|
+
_ensureInitialized();
|
|
287
|
+
|
|
288
|
+
_addInteraction(eventName, {
|
|
289
|
+
...properties,
|
|
290
|
+
screen: currentScreen,
|
|
291
|
+
timestamp: new Date().toISOString(),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
console.log(`[MimzTracker] 📊 Event: ${eventName}`, properties);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Track a conversion
|
|
299
|
+
*
|
|
300
|
+
* @param {object} conversionData - Conversion details
|
|
301
|
+
* @param {string} [conversionData.productName] - Product or conversion name
|
|
302
|
+
* @param {number} [conversionData.value] - Conversion value
|
|
303
|
+
* @param {string} [conversionData.currency] - Currency code (default: 'USD')
|
|
304
|
+
* @param {string} [conversionData.conversionKey] - Unique conversion identifier
|
|
305
|
+
* @param {string} [conversionData.level] - Conversion level/type
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* MimzTracker.trackConversion({
|
|
309
|
+
* productName: 'Premium Plan',
|
|
310
|
+
* value: 49.99,
|
|
311
|
+
* currency: 'USD',
|
|
312
|
+
* conversionKey: 'premium_signup',
|
|
313
|
+
* level: 'Purchase',
|
|
314
|
+
* });
|
|
315
|
+
*/
|
|
316
|
+
const trackConversion = async (conversionData = {}) => {
|
|
317
|
+
_ensureInitialized();
|
|
318
|
+
|
|
319
|
+
const payload = {
|
|
320
|
+
userId: clientId,
|
|
321
|
+
visitorId,
|
|
322
|
+
visitId,
|
|
323
|
+
product_name: conversionData.productName || '',
|
|
324
|
+
conversion_value: conversionData.value || 0,
|
|
325
|
+
currency: conversionData.currency || 'USD',
|
|
326
|
+
conversion_key: conversionData.conversionKey || `app_conversion_${Date.now()}`,
|
|
327
|
+
conversion_date: new Date().toISOString().split('T')[0],
|
|
328
|
+
level: conversionData.level || 'App Conversion',
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const result = await network.post(API_ENDPOINTS.ADD_CONVERSION, payload);
|
|
332
|
+
|
|
333
|
+
_addInteraction('conversion', {
|
|
334
|
+
...conversionData,
|
|
335
|
+
timestamp: new Date().toISOString(),
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
console.log('[MimzTracker] 💰 Conversion tracked:', conversionData.productName || 'unnamed');
|
|
339
|
+
return result;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Track a contact (form submission equivalent)
|
|
344
|
+
* Use this when a user submits their contact information in the app
|
|
345
|
+
*
|
|
346
|
+
* @param {object} contactData - Contact details
|
|
347
|
+
* @param {string} [contactData.email] - Email address
|
|
348
|
+
* @param {string} [contactData.name] - Full name
|
|
349
|
+
* @param {string} [contactData.firstName] - First name
|
|
350
|
+
* @param {string} [contactData.lastName] - Last name
|
|
351
|
+
* @param {string} [contactData.phone] - Phone number
|
|
352
|
+
* @param {string} [contactData.source] - Where the contact was captured
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* MimzTracker.trackContact({
|
|
356
|
+
* email: 'user@example.com',
|
|
357
|
+
* name: 'John Doe',
|
|
358
|
+
* phone: '+1234567890',
|
|
359
|
+
* source: 'Signup Form',
|
|
360
|
+
* });
|
|
361
|
+
*/
|
|
362
|
+
const trackContact = async (contactData = {}) => {
|
|
363
|
+
_ensureInitialized();
|
|
364
|
+
|
|
365
|
+
// Store user data persistently
|
|
366
|
+
if (contactData.email) await storage.set('email', contactData.email);
|
|
367
|
+
if (contactData.name || contactData.firstName) {
|
|
368
|
+
await storage.set('fname', contactData.firstName || contactData.name);
|
|
369
|
+
}
|
|
370
|
+
if (contactData.phone) await storage.set('phone', contactData.phone);
|
|
371
|
+
|
|
372
|
+
// Send email submission to the backend (this creates both a Contact and Conversion)
|
|
373
|
+
const payload = {
|
|
374
|
+
email: contactData.email || '',
|
|
375
|
+
name: contactData.name || `${contactData.firstName || ''} ${contactData.lastName || ''}`.trim(),
|
|
376
|
+
clientId,
|
|
377
|
+
visitorId,
|
|
378
|
+
visitId: String(visitId),
|
|
379
|
+
campaignType: 'App Form',
|
|
380
|
+
campaignAction: contactData.source || 'app_contact',
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const result = await network.post(API_ENDPOINTS.EMAIL_SUBMISSION, payload);
|
|
384
|
+
|
|
385
|
+
_addInteraction('contact_submitted', {
|
|
386
|
+
hasEmail: !!contactData.email,
|
|
387
|
+
hasPhone: !!contactData.phone,
|
|
388
|
+
hasName: !!(contactData.name || contactData.firstName),
|
|
389
|
+
source: contactData.source,
|
|
390
|
+
timestamp: new Date().toISOString(),
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
console.log('[MimzTracker] 👤 Contact tracked');
|
|
394
|
+
return result;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Identify a user by setting their personal details
|
|
399
|
+
* This data will be attached to all future tracking calls
|
|
400
|
+
*
|
|
401
|
+
* @param {object} userData - User details
|
|
402
|
+
* @param {string} [userData.email] - Email
|
|
403
|
+
* @param {string} [userData.name] - Name
|
|
404
|
+
* @param {string} [userData.phone] - Phone
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* MimzTracker.identify({ email: 'user@example.com', name: 'John' });
|
|
408
|
+
*/
|
|
409
|
+
const identify = async (userData = {}) => {
|
|
410
|
+
_ensureInitialized();
|
|
411
|
+
|
|
412
|
+
if (userData.email) await storage.set('email', userData.email);
|
|
413
|
+
if (userData.name) await storage.set('fname', userData.name);
|
|
414
|
+
if (userData.phone) await storage.set('phone', userData.phone);
|
|
415
|
+
|
|
416
|
+
// Re-post visitor data with user info
|
|
417
|
+
const trackingData = await _buildTrackingPayload();
|
|
418
|
+
await _postVisitorData(trackingData);
|
|
419
|
+
|
|
420
|
+
console.log('[MimzTracker] 🆔 User identified');
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Manually set UTM parameters (for cases where deep link isn't used)
|
|
425
|
+
*
|
|
426
|
+
* @param {object} utms - UTM parameters
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* MimzTracker.setUtmParams({
|
|
430
|
+
* utm_source: 'facebook',
|
|
431
|
+
* utm_medium: 'cpc',
|
|
432
|
+
* utm_campaign: 'summer_sale',
|
|
433
|
+
* });
|
|
434
|
+
*/
|
|
435
|
+
const setUtmParams = async (utms = {}) => {
|
|
436
|
+
_ensureInitialized();
|
|
437
|
+
|
|
438
|
+
currentUtmParams = { ...currentUtmParams, ...utms };
|
|
439
|
+
await storage.setJSON('currentUtm', currentUtmParams);
|
|
440
|
+
|
|
441
|
+
// Store first-touch if empty
|
|
442
|
+
if (Object.keys(firstTouchUtmParams).length === 0) {
|
|
443
|
+
firstTouchUtmParams = { ...utms };
|
|
444
|
+
await storage.setJSON('firstTouchUtm', firstTouchUtmParams);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
console.log('[MimzTracker] 🏷️ UTM params set:', utms);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get the current visitor ID
|
|
452
|
+
* @returns {string|null}
|
|
453
|
+
*/
|
|
454
|
+
const getVisitorId = () => visitorId;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get the current visit ID
|
|
458
|
+
* @returns {number|null}
|
|
459
|
+
*/
|
|
460
|
+
const getVisitId = () => visitId;
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Get current session duration in seconds
|
|
464
|
+
* @returns {number}
|
|
465
|
+
*/
|
|
466
|
+
const getSessionDuration = () => {
|
|
467
|
+
if (!sessionStartTime) return 0;
|
|
468
|
+
return Math.floor((Date.now() - sessionStartTime) / 1000);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Flush: send current interactions to backend immediately
|
|
473
|
+
* Call this on app background/close
|
|
474
|
+
*/
|
|
475
|
+
const flush = async () => {
|
|
476
|
+
if (!isInitialized) return;
|
|
477
|
+
|
|
478
|
+
const trackingData = await _buildTrackingPayload();
|
|
479
|
+
trackingData.interactions = [...interactions];
|
|
480
|
+
await _postVisitorData(trackingData);
|
|
481
|
+
|
|
482
|
+
console.log('[MimzTracker] 📤 Data flushed');
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Reset the tracker (clear all stored data)
|
|
487
|
+
* Useful for logout flows
|
|
488
|
+
*/
|
|
489
|
+
const reset = async () => {
|
|
490
|
+
await storage.clearAll();
|
|
491
|
+
|
|
492
|
+
isInitialized = false;
|
|
493
|
+
clientId = null;
|
|
494
|
+
visitorId = null;
|
|
495
|
+
visitId = null;
|
|
496
|
+
sessionStartTime = null;
|
|
497
|
+
sessionCount = 0;
|
|
498
|
+
currentScreen = null;
|
|
499
|
+
firstSeenTimestamp = null;
|
|
500
|
+
cachedIp = null;
|
|
501
|
+
cachedDeviceInfo = null;
|
|
502
|
+
currentUtmParams = {};
|
|
503
|
+
firstTouchUtmParams = {};
|
|
504
|
+
firstTrafficSource = null;
|
|
505
|
+
interactions = [];
|
|
506
|
+
|
|
507
|
+
console.log('[MimzTracker] 🔄 Tracker reset');
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// ─── Internal Helpers ────────────────────────────────────────────────────────
|
|
511
|
+
|
|
512
|
+
const _ensureInitialized = () => {
|
|
513
|
+
if (!isInitialized) {
|
|
514
|
+
throw new Error('[MimzTracker] Not initialized. Call MimzTracker.init() first.');
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const _addInteraction = (type, data) => {
|
|
519
|
+
interactions.push({
|
|
520
|
+
type,
|
|
521
|
+
data,
|
|
522
|
+
screen: currentScreen,
|
|
523
|
+
timestamp: new Date().toISOString(),
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Limit array size to prevent memory issues (same as web script)
|
|
527
|
+
if (interactions.length > 50) {
|
|
528
|
+
interactions = interactions.slice(-50);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const _buildTrackingPayload = async () => {
|
|
533
|
+
const email = await storage.get('email');
|
|
534
|
+
const fname = await storage.get('fname');
|
|
535
|
+
const phone = await storage.get('phone');
|
|
536
|
+
|
|
537
|
+
return {
|
|
538
|
+
// Identifiers
|
|
539
|
+
clientId,
|
|
540
|
+
visitorId,
|
|
541
|
+
visitId,
|
|
542
|
+
urlpt_ip: cachedIp || '0.0.0.0',
|
|
543
|
+
gaclientid: null, // Not available in native apps
|
|
544
|
+
|
|
545
|
+
// Current UTM parameters
|
|
546
|
+
utm_source: currentUtmParams.utm_source || null,
|
|
547
|
+
utm_medium: currentUtmParams.utm_medium || null,
|
|
548
|
+
utm_campaign: currentUtmParams.utm_campaign || null,
|
|
549
|
+
utm_term: currentUtmParams.utm_term || null,
|
|
550
|
+
utm_content: currentUtmParams.utm_content || null,
|
|
551
|
+
|
|
552
|
+
// First touch UTM parameters
|
|
553
|
+
first_utm_source: firstTouchUtmParams.utm_source || null,
|
|
554
|
+
first_utm_medium: firstTouchUtmParams.utm_medium || null,
|
|
555
|
+
first_utm_campaign: firstTouchUtmParams.utm_campaign || null,
|
|
556
|
+
first_utm_term: firstTouchUtmParams.utm_term || null,
|
|
557
|
+
first_utm_content: firstTouchUtmParams.utm_content || null,
|
|
558
|
+
|
|
559
|
+
// Click IDs
|
|
560
|
+
fbclid: currentUtmParams.fbclid || null,
|
|
561
|
+
msclkid: currentUtmParams.msclkid || null,
|
|
562
|
+
gclid: currentUtmParams.gclid || null,
|
|
563
|
+
|
|
564
|
+
// App info (replaces landing page / URL info)
|
|
565
|
+
urlpt_original_ref: null,
|
|
566
|
+
urlpt_landing_page: appName ? `app://${appName}` : null,
|
|
567
|
+
urlpt_landing_page_base: appName || null,
|
|
568
|
+
|
|
569
|
+
// Referrer info (set via deep link tracking)
|
|
570
|
+
urlpt_ref: null,
|
|
571
|
+
urlpt_ref_domain: null,
|
|
572
|
+
|
|
573
|
+
// Current screen info (replaces current page info)
|
|
574
|
+
urlpt_url: currentScreen ? `app://${currentScreen}` : `app://${appName || 'main'}`,
|
|
575
|
+
urlpt_url_base: currentScreen || appName || 'main',
|
|
576
|
+
domain: appName || 'native-app',
|
|
577
|
+
|
|
578
|
+
// Traffic sources
|
|
579
|
+
traffic_source: firstTrafficSource || 'Direct',
|
|
580
|
+
first_traffic_source: firstTrafficSource || 'Direct',
|
|
581
|
+
organic_source: null,
|
|
582
|
+
organic_source_str: null,
|
|
583
|
+
|
|
584
|
+
// User data
|
|
585
|
+
email: email || null,
|
|
586
|
+
fname: fname || null,
|
|
587
|
+
phone: phone || null,
|
|
588
|
+
|
|
589
|
+
// Device and technical info
|
|
590
|
+
user_agent: cachedDeviceInfo?.userAgent || 'MimzSDK/1.0',
|
|
591
|
+
deviceInfo: cachedDeviceInfo,
|
|
592
|
+
utm_device: cachedDeviceInfo?.deviceType || 'Mobile',
|
|
593
|
+
utm_devicemodel: cachedDeviceInfo?.deviceModel || 'Unknown',
|
|
594
|
+
|
|
595
|
+
// Facebook tracking (not available in native apps without FB SDK)
|
|
596
|
+
_fbc: null,
|
|
597
|
+
_fbp: null,
|
|
598
|
+
|
|
599
|
+
// Timestamps
|
|
600
|
+
firstSeen: firstSeenTimestamp,
|
|
601
|
+
lastSeen: new Date().toISOString(),
|
|
602
|
+
|
|
603
|
+
// Session info
|
|
604
|
+
sessionDuration: getSessionDuration(),
|
|
605
|
+
sessionCount,
|
|
606
|
+
|
|
607
|
+
// Interactions
|
|
608
|
+
interactions,
|
|
609
|
+
|
|
610
|
+
// App metadata
|
|
611
|
+
appName: appName,
|
|
612
|
+
appVersion: appVersion,
|
|
613
|
+
platform: 'react-native',
|
|
614
|
+
new: true,
|
|
615
|
+
};
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const _postVisitorData = async (trackingData) => {
|
|
619
|
+
try {
|
|
620
|
+
const result = await network.post(API_ENDPOINTS.ADD_VISITORS, trackingData);
|
|
621
|
+
if (result) {
|
|
622
|
+
console.log('[MimzTracker] ✅ Visitor data sent');
|
|
623
|
+
}
|
|
624
|
+
return result;
|
|
625
|
+
} catch (error) {
|
|
626
|
+
console.warn('[MimzTracker] ⚠️ Failed to send visitor data:', error.message);
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
632
|
+
|
|
633
|
+
module.exports = {
|
|
634
|
+
init,
|
|
635
|
+
trackDeepLink,
|
|
636
|
+
trackScreenView,
|
|
637
|
+
trackEvent,
|
|
638
|
+
trackConversion,
|
|
639
|
+
trackContact,
|
|
640
|
+
identify,
|
|
641
|
+
setUtmParams,
|
|
642
|
+
getVisitorId,
|
|
643
|
+
getVisitId,
|
|
644
|
+
getSessionDuration,
|
|
645
|
+
flush,
|
|
646
|
+
reset,
|
|
647
|
+
};
|