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/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
+ };