user-analytics-tracker 1.2.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 ADDED
@@ -0,0 +1,696 @@
1
+ # user-analytics-tracker
2
+
3
+ [![npm version](https://badge.fury.io/js/user-analytics-tracker.svg)](https://www.npmjs.com/package/user-analytics-tracker)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![CI](https://github.com/switch-org/analytics-tracker/actions/workflows/ci.yml/badge.svg)](https://github.com/switch-org/analytics-tracker/actions/workflows/ci.yml)
6
+
7
+ A comprehensive, lightweight analytics tracking library for React applications. Track device information, network type, user location, attribution data, and moreβ€”all with zero runtime dependencies (React as peer dependency).
8
+
9
+ **πŸ”’ Privacy-First & Self-Hosted**: All analytics data is sent to **your own backend server**. No data is sent to third-party servers. You have full control over your analytics data.
10
+
11
+ ## ✨ Features
12
+
13
+ - πŸ” **Device Detection**: Automatically detects device type, OS, browser, model, brand, and hardware specs using User-Agent Client Hints
14
+ - 🌐 **Network Detection**: Identifies WiFi, Cellular, Hotspot, Ethernet connections with quality metrics
15
+ - πŸ“ **Location Tracking**:
16
+ - **Automatic IP-based location** (no permission required) - works immediately
17
+ - GPS location with consent management (MSISDN-based consent)
18
+ - Includes public IP address, country, city, region, timezone
19
+ - Automatic fallback from GPS to IP when GPS unavailable
20
+ - 🎯 **Attribution Tracking**: UTM parameters, referrer tracking, first/last touch attribution
21
+ - πŸ“Š **IP Geolocation**: Client-side and server-side IP-based location detection utilities
22
+ - πŸ”’ **Privacy-First**: Location consent management, automatic IP fallback
23
+ - ⚑ **Lightweight**: Zero runtime dependencies (except React)
24
+ - πŸ“¦ **TypeScript**: Fully typed with comprehensive type definitions
25
+ - 🎨 **Framework Agnostic Core**: Core detectors work without React
26
+ - πŸ§ͺ **Well Tested**: Comprehensive test suite with Vitest
27
+
28
+ ## πŸ“¦ Installation
29
+
30
+ ```bash
31
+ npm install user-analytics-tracker react react-dom
32
+ # or
33
+ yarn add user-analytics-tracker react react-dom
34
+ # or
35
+ pnpm add user-analytics-tracker react react-dom
36
+ ```
37
+
38
+ **Note**: React and React-DOM are peer dependencies and must be installed separately.
39
+
40
+ ## πŸ”’ Self-Hosted Analytics - Configure Your Backend URL
41
+
42
+ **All analytics data is sent to YOUR backend server** - no third-party servers involved. You have complete control over your data.
43
+
44
+ ### Quick Configuration
45
+
46
+ Simply provide your backend URL in the `apiEndpoint` configuration:
47
+
48
+ ```tsx
49
+ import { useAnalytics } from 'user-analytics-tracker';
50
+
51
+ function App() {
52
+ const analytics = useAnalytics({
53
+ config: {
54
+ apiEndpoint: 'https://api.yourcompany.com/analytics', // Your backend URL
55
+ },
56
+ });
57
+ }
58
+ ```
59
+
60
+ ### Configuration Options
61
+
62
+ You can configure your backend URL in three ways:
63
+
64
+ #### 1. **Full URL (Recommended for Production)**
65
+
66
+ Use a complete URL pointing to your backend server:
67
+
68
+ ```tsx
69
+ const analytics = useAnalytics({
70
+ config: {
71
+ // Point to your own server
72
+ apiEndpoint: 'https://api.yourcompany.com/analytics',
73
+
74
+ // Or with a custom port
75
+ // apiEndpoint: 'https://api.yourcompany.com:8080/analytics',
76
+
77
+ // Or using a subdomain
78
+ // apiEndpoint: 'https://analytics.yourcompany.com/track',
79
+ },
80
+ });
81
+ ```
82
+
83
+ #### 2. **Relative Path (Same Domain)**
84
+
85
+ Use a relative path if your API is on the same domain as your frontend:
86
+
87
+ ```tsx
88
+ const analytics = useAnalytics({
89
+ config: {
90
+ // Sends to: https://yourdomain.com/api/analytics
91
+ apiEndpoint: '/api/analytics',
92
+ },
93
+ });
94
+ ```
95
+
96
+ #### 3. **Environment Variables (Best Practice)**
97
+
98
+ Use environment variables for different environments:
99
+
100
+ ```tsx
101
+ // .env.local (development)
102
+ // NEXT_PUBLIC_ANALYTICS_API=https://api-dev.yourcompany.com/analytics
103
+
104
+ // .env.production
105
+ // NEXT_PUBLIC_ANALYTICS_API=https://api.yourcompany.com/analytics
106
+
107
+ const analytics = useAnalytics({
108
+ config: {
109
+ apiEndpoint: process.env.NEXT_PUBLIC_ANALYTICS_API || '/api/analytics',
110
+ },
111
+ });
112
+ ```
113
+
114
+ ### Step-by-Step Setup
115
+
116
+ 1. **Set up your backend API endpoint** (see [Backend Setup](#-backend-api-setup) below)
117
+ 2. **Configure the frontend** with your backend URL
118
+ 3. **Test the connection** using browser DevTools Network tab
119
+
120
+ ### Examples by Framework
121
+
122
+ **React (Create React App)**
123
+ ```tsx
124
+ // src/App.tsx
125
+ import { useAnalytics } from 'user-analytics-tracker';
126
+
127
+ function App() {
128
+ const analytics = useAnalytics({
129
+ config: {
130
+ apiEndpoint: process.env.REACT_APP_ANALYTICS_API || 'https://api.yourcompany.com/analytics',
131
+ },
132
+ });
133
+ }
134
+ ```
135
+
136
+ **Next.js**
137
+ ```tsx
138
+ // app/layout.tsx or pages/_app.tsx
139
+ import { useAnalytics } from 'user-analytics-tracker';
140
+
141
+ export default function Layout() {
142
+ useAnalytics({
143
+ config: {
144
+ apiEndpoint: process.env.NEXT_PUBLIC_ANALYTICS_API || '/api/analytics',
145
+ },
146
+ });
147
+ }
148
+ ```
149
+
150
+ **Vite + React**
151
+ ```tsx
152
+ // src/main.tsx
153
+ import { useAnalytics } from 'user-analytics-tracker';
154
+
155
+ function App() {
156
+ useAnalytics({
157
+ config: {
158
+ apiEndpoint: import.meta.env.VITE_ANALYTICS_API || 'https://api.yourcompany.com/analytics',
159
+ },
160
+ });
161
+ }
162
+ ```
163
+
164
+ ## πŸš€ Quick Start
165
+
166
+ ### Basic Usage (React Hook)
167
+
168
+ ```tsx
169
+ import { useAnalytics } from 'user-analytics-tracker';
170
+
171
+ function MyApp() {
172
+ const { sessionId, networkInfo, deviceInfo, location, logEvent } = useAnalytics({
173
+ autoSend: true,
174
+ config: {
175
+ // Use your own backend server (full URL)
176
+ apiEndpoint: 'https://api.yourcompany.com/analytics',
177
+ // Or use relative path (same domain)
178
+ // apiEndpoint: '/api/analytics',
179
+ },
180
+ });
181
+
182
+ return (
183
+ <div>
184
+ <p>Device: {deviceInfo?.deviceBrand} {deviceInfo?.deviceModel}</p>
185
+ <p>Network: {networkInfo?.type}</p>
186
+ <button onClick={() => logEvent({ action: 'button_click' })}>
187
+ Track Click
188
+ </button>
189
+ </div>
190
+ );
191
+ }
192
+ ```
193
+
194
+ ### Standalone Detectors (No React)
195
+
196
+ ```typescript
197
+ import {
198
+ NetworkDetector,
199
+ DeviceDetector,
200
+ AttributionDetector,
201
+ LocationDetector,
202
+ } from 'user-analytics-tracker';
203
+
204
+ // Detect network type
205
+ const network = NetworkDetector.detect();
206
+ console.log(network.type); // 'wifi' | 'cellular' | 'hotspot' | 'ethernet' | 'unknown'
207
+
208
+ // Detect device info
209
+ const device = await DeviceDetector.detect();
210
+ console.log(device.deviceBrand, device.deviceModel);
211
+
212
+ // Detect attribution (UTM params, referrer, etc.)
213
+ const attribution = AttributionDetector.detect();
214
+ console.log(attribution.utm_source);
215
+
216
+ // Detect location (automatic IP-based if no consent, GPS if consent granted)
217
+ const location = await LocationDetector.detect();
218
+ console.log(location.lat, location.lon);
219
+ console.log(location.ip); // Public IP (when using IP-based location)
220
+ console.log(location.country, location.city); // Location details
221
+
222
+ // Or get IP-based location only (no permission needed)
223
+ const ipLocation = await LocationDetector.detectIPOnly();
224
+ console.log(ipLocation.ip, ipLocation.country, ipLocation.city);
225
+ ```
226
+
227
+ ## πŸ“š API Reference
228
+
229
+ ### React Hook: `useAnalytics`
230
+
231
+ The main React hook for analytics tracking.
232
+
233
+ #### Parameters
234
+
235
+ ```typescript
236
+ useAnalytics(options?: UseAnalyticsOptions): UseAnalyticsReturn
237
+ ```
238
+
239
+ **Options:**
240
+
241
+ ```typescript
242
+ interface UseAnalyticsOptions {
243
+ autoSend?: boolean; // Auto-send analytics on mount (default: true)
244
+ config?: Partial<AnalyticsConfig>;
245
+ onReady?: (data: {
246
+ sessionId: string;
247
+ networkInfo: NetworkInfo;
248
+ deviceInfo: DeviceInfo;
249
+ location: LocationInfo;
250
+ attribution: AttributionInfo;
251
+ }) => void; // Callback when data is ready
252
+ }
253
+ ```
254
+
255
+ #### Returns
256
+
257
+ ```typescript
258
+ interface UseAnalyticsReturn {
259
+ sessionId: string | null;
260
+ networkInfo: NetworkInfo | null;
261
+ deviceInfo: DeviceInfo | null;
262
+ location: LocationInfo | null;
263
+ attribution: AttributionInfo | null;
264
+ pageVisits: number;
265
+ interactions: number;
266
+ logEvent: (customData?: Record<string, any>) => Promise<void>;
267
+ incrementInteraction: () => void;
268
+ refresh: () => Promise<{
269
+ net: NetworkInfo;
270
+ dev: DeviceInfo;
271
+ attr: AttributionInfo;
272
+ loc: LocationInfo;
273
+ }>;
274
+ }
275
+ ```
276
+
277
+ ### Detectors
278
+
279
+ #### `NetworkDetector.detect()`
280
+
281
+ Detects network connection type and quality.
282
+
283
+ ```typescript
284
+ const network = NetworkDetector.detect();
285
+ // Returns:
286
+ // {
287
+ // type: 'wifi' | 'cellular' | 'hotspot' | 'ethernet' | 'unknown';
288
+ // effectiveType?: string; // '2g', '3g', '4g', etc.
289
+ // downlink?: number; // Mbps
290
+ // rtt?: number; // ms
291
+ // saveData?: boolean;
292
+ // connectionType?: string;
293
+ // }
294
+ ```
295
+
296
+ #### `DeviceDetector.detect()`
297
+
298
+ Detects device information (async - uses User-Agent Client Hints).
299
+
300
+ ```typescript
301
+ const device = await DeviceDetector.detect();
302
+ // Returns:
303
+ // {
304
+ // type: 'mobile' | 'tablet' | 'desktop' | 'unknown';
305
+ // os: string;
306
+ // osVersion: string;
307
+ // browser: string;
308
+ // browserVersion: string;
309
+ // deviceModel: string;
310
+ // deviceBrand: string;
311
+ // screenResolution: string;
312
+ // // ... more fields
313
+ // }
314
+ ```
315
+
316
+ #### `LocationDetector.detect()`
317
+
318
+ Detects location (IP-first when no consent, GPS when consent granted). Automatically falls back to IP if GPS fails.
319
+
320
+ ```typescript
321
+ const location = await LocationDetector.detect();
322
+ // Returns:
323
+ // {
324
+ // lat?: number | null;
325
+ // lon?: number | null;
326
+ // accuracy?: number | null; // GPS only
327
+ // permission: 'granted' | 'denied' | 'prompt' | 'unsupported';
328
+ // source: 'gps' | 'ip' | 'unknown';
329
+ // ts?: string;
330
+ // // IP-based location includes:
331
+ // ip?: string | null; // Public IP address
332
+ // country?: string; // Country name
333
+ // countryCode?: string; // ISO country code
334
+ // city?: string; // City name
335
+ // region?: string; // Region/state
336
+ // timezone?: string; // Timezone
337
+ // }
338
+ ```
339
+
340
+ #### `LocationDetector.detectIPOnly()`
341
+
342
+ Get IP-based location only (fast, automatic, no permission needed).
343
+
344
+ ```typescript
345
+ const location = await LocationDetector.detectIPOnly();
346
+ // Returns IP-based location with IP address, country, city, coordinates
347
+ // Works immediately without user permission
348
+ ```
349
+
350
+ #### `LocationDetector.detectWithAutoConsent()`
351
+
352
+ Automatically grants consent and tries GPS, falls back to IP if GPS fails.
353
+
354
+ ```typescript
355
+ const location = await LocationDetector.detectWithAutoConsent();
356
+ // 1. Automatically grants location consent
357
+ // 2. Tries GPS location (if available)
358
+ // 3. Falls back to IP-based location if GPS fails/denied/unavailable
359
+ ```
360
+
361
+ #### `getPublicIP()`
362
+
363
+ Get just the public IP address (utility function).
364
+
365
+ ```typescript
366
+ import { getPublicIP } from 'user-analytics-tracker';
367
+
368
+ const ip = await getPublicIP();
369
+ console.log(ip); // "203.0.113.42"
370
+ ```
371
+
372
+ #### `AttributionDetector.detect()`
373
+
374
+ Detects UTM parameters, referrer, and session tracking.
375
+
376
+ ```typescript
377
+ const attribution = AttributionDetector.detect();
378
+ // Returns:
379
+ // {
380
+ // landingUrl: string;
381
+ // referrerUrl: string | null;
382
+ // referrerDomain: string | null;
383
+ // utm_source?: string | null;
384
+ // utm_medium?: string | null;
385
+ // utm_campaign?: string | null;
386
+ // // ... more UTM fields
387
+ // firstTouch?: Record<string, string | null> | null;
388
+ // lastTouch?: Record<string, string | null> | null;
389
+ // sessionStart?: string | null;
390
+ // }
391
+ ```
392
+
393
+ ### Services
394
+
395
+ #### `AnalyticsService.trackUserJourney()`
396
+
397
+ Send analytics data to your backend.
398
+
399
+ ```typescript
400
+ import { AnalyticsService } from 'user-analytics-tracker';
401
+
402
+ // Configure endpoint - use your own server
403
+ AnalyticsService.configure({
404
+ apiEndpoint: 'https://api.yourcompany.com/analytics'
405
+ });
406
+
407
+ // Or use relative path (same domain)
408
+ // AnalyticsService.configure({ apiEndpoint: '/api/analytics' });
409
+
410
+ // Track event
411
+ await AnalyticsService.trackUserJourney({
412
+ sessionId: 'abc123',
413
+ pageUrl: 'https://example.com/page',
414
+ networkInfo: network,
415
+ deviceInfo: device,
416
+ location: location,
417
+ attribution: attribution,
418
+ customData: { userId: 'user123', action: 'purchase' },
419
+ });
420
+ ```
421
+
422
+ ### Utilities
423
+
424
+ #### Location Consent Management
425
+
426
+ ```typescript
427
+ import {
428
+ setLocationConsentGranted,
429
+ hasLocationConsent,
430
+ checkAndSetLocationConsent,
431
+ clearLocationConsent,
432
+ } from 'user-analytics-tracker';
433
+
434
+ // When user enters MSISDN, grant location consent
435
+ checkAndSetLocationConsent(msisdn); // Returns true if consent granted
436
+
437
+ // Check if consent exists
438
+ if (hasLocationConsent()) {
439
+ // Location tracking allowed
440
+ }
441
+
442
+ // Manually grant/revoke consent
443
+ setLocationConsentGranted();
444
+ clearLocationConsent();
445
+ ```
446
+
447
+ #### IP Geolocation Utilities
448
+
449
+ **Client-Side: Get Public IP**
450
+
451
+ ```typescript
452
+ import { getPublicIP } from 'user-analytics-tracker';
453
+
454
+ // Get just the public IP address (no location data)
455
+ const ip = await getPublicIP();
456
+ console.log('Your IP:', ip); // "203.0.113.42"
457
+ ```
458
+
459
+ **Server-Side: IP Location from Request**
460
+
461
+ ```typescript
462
+ import { getIPLocation, getIPFromRequest } from 'user-analytics-tracker';
463
+
464
+ // In your API route (Next.js example)
465
+ export async function POST(req: Request) {
466
+ // Extract IP from request headers
467
+ const ip = getIPFromRequest(req);
468
+
469
+ // Get location data from IP
470
+ const location = await getIPLocation(ip);
471
+ // location contains: country, region, city, lat, lon, timezone, isp, etc.
472
+ }
473
+ ```
474
+
475
+ ## πŸ”’ Privacy & Consent
476
+
477
+ ### MSISDN-Based Consent
478
+
479
+ When a user enters their phone number (MSISDN), it implies consent for location tracking. The library automatically grants location consent:
480
+
481
+ ```typescript
482
+ import { checkAndSetLocationConsent } from 'user-analytics-tracker';
483
+
484
+ // When MSISDN is entered
485
+ checkAndSetLocationConsent(phoneNumber);
486
+ // Location consent is now granted, GPS will be requested automatically
487
+ ```
488
+
489
+ ### Hotspot Detection & Gating
490
+
491
+ Detect and restrict hotspot users:
492
+
493
+ ```tsx
494
+ import { useAnalytics } from 'user-analytics-tracker';
495
+
496
+ function HotspotGate({ children }) {
497
+ const { networkInfo } = useAnalytics({ autoSend: false });
498
+
499
+ if (networkInfo?.type === 'hotspot') {
500
+ return (
501
+ <div>
502
+ <h2>Hotspot Detected</h2>
503
+ <p>Please switch to mobile data or Wi-Fi.</p>
504
+ </div>
505
+ );
506
+ }
507
+
508
+ return children;
509
+ }
510
+ ```
511
+
512
+ ## πŸ“– Advanced Usage
513
+
514
+ ### Custom Analytics Service
515
+
516
+ ```typescript
517
+ import { AnalyticsService } from 'user-analytics-tracker';
518
+
519
+ class MyAnalyticsService extends AnalyticsService {
520
+ static async trackUserJourney(data: any) {
521
+ // Custom tracking logic
522
+ await fetch('/my-custom-endpoint', {
523
+ method: 'POST',
524
+ body: JSON.stringify(data),
525
+ });
526
+ }
527
+ }
528
+ ```
529
+
530
+ ### Manual Event Tracking
531
+
532
+ ```typescript
533
+ const { logEvent, incrementInteraction } = useAnalytics();
534
+
535
+ // Log custom event
536
+ await logEvent({
537
+ eventType: 'purchase',
538
+ productId: '123',
539
+ amount: 99.99,
540
+ });
541
+
542
+ // Increment interaction counter
543
+ incrementInteraction();
544
+ ```
545
+
546
+ ### Server-Side Integration
547
+
548
+ Example Next.js API route:
549
+
550
+ ```typescript
551
+ // app/api/analytics/route.ts
552
+ import { NextRequest, NextResponse } from 'next/server';
553
+ import { getIPFromRequest, getIPLocation } from 'user-analytics-tracker';
554
+
555
+ export async function POST(req: NextRequest) {
556
+ const body = await req.json();
557
+ const ip = getIPFromRequest(req);
558
+ const ipLocation = await getIPLocation(ip);
559
+
560
+ // Store analytics with IP location
561
+ await storeAnalytics({
562
+ ...body,
563
+ ip,
564
+ ipLocation,
565
+ });
566
+
567
+ return NextResponse.json({ ok: true });
568
+ }
569
+ ```
570
+
571
+ ## πŸ“š Documentation
572
+
573
+ Comprehensive documentation is available in the [`docs/`](./docs) directory:
574
+
575
+ - **[Usage Guide](./docs/usage-guide.md)** - Complete guide on how to use the package in your applications
576
+ - Installation instructions
577
+ - Basic and advanced usage examples
578
+ - React hook documentation
579
+ - Standalone (non-React) usage
580
+ - Framework integrations (Next.js, Gatsby, etc.)
581
+ - Real-world examples
582
+ - Troubleshooting
583
+
584
+ - **[Quick Start Guide](./docs/quick-start.md)** - Get started in 5 minutes
585
+ - Installation
586
+ - Basic setup
587
+ - Development workflow
588
+ - Common commands
589
+
590
+ - **[Publishing Guide](./docs/publishing.md)** - How to publish the package
591
+ - Prerequisites
592
+ - Publishing methods (automatic & manual)
593
+ - Version management
594
+ - Best practices
595
+
596
+ - **[Package Structure](./docs/package-structure.md)** - Understanding the codebase
597
+ - Directory structure
598
+ - Architecture overview
599
+ - Code organization
600
+ - Development guidelines
601
+
602
+ ## πŸ§ͺ Testing
603
+
604
+ ```bash
605
+ # Run tests
606
+ npm test
607
+
608
+ # Run tests in watch mode
609
+ npm run test:watch
610
+
611
+ # Run tests with coverage
612
+ npm run test:coverage
613
+ ```
614
+
615
+ ## πŸ› οΈ Development
616
+
617
+ ```bash
618
+ # Clone repository
619
+ git clone https://github.com/switch-org/analytics-tracker.git
620
+ cd analytics-tracker
621
+
622
+ # Install dependencies
623
+ npm install
624
+
625
+ # Build
626
+ npm run build
627
+
628
+ # Watch mode
629
+ npm run build:watch
630
+
631
+ # Lint
632
+ npm run lint
633
+
634
+ # Format
635
+ npm run format
636
+
637
+ # Type check
638
+ npm run type-check
639
+ ```
640
+
641
+ ## πŸ“ TypeScript
642
+
643
+ This package is written in TypeScript and provides full type definitions. All exports are fully typed:
644
+
645
+ ```typescript
646
+ import type {
647
+ NetworkInfo,
648
+ DeviceInfo,
649
+ LocationInfo,
650
+ AttributionInfo,
651
+ IPLocation,
652
+ UseAnalyticsReturn,
653
+ } from 'user-analytics-tracker';
654
+ ```
655
+
656
+ ## 🀝 Contributing
657
+
658
+ Contributions are welcome! Please read our contributing guidelines first.
659
+
660
+ 1. Fork the repository
661
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
662
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
663
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
664
+ 5. Open a Pull Request
665
+
666
+ ### Commit Convention
667
+
668
+ We follow [Conventional Commits](https://www.conventionalcommits.org/):
669
+
670
+ - `feat:` New feature
671
+ - `fix:` Bug fix
672
+ - `docs:` Documentation changes
673
+ - `style:` Code style changes (formatting, etc.)
674
+ - `refactor:` Code refactoring
675
+ - `test:` Adding or updating tests
676
+ - `chore:` Maintenance tasks
677
+
678
+ ## πŸ“„ License
679
+
680
+ MIT Β© [Switch Org](https://github.com/switch-org)
681
+
682
+ ## πŸ™ Acknowledgments
683
+
684
+ - Uses [ip-api.com](http://ip-api.com) for free IP geolocation
685
+ - Built with modern web APIs (User-Agent Client Hints, Network Information API, Geolocation API)
686
+
687
+ <!-- ## πŸ“ž Support
688
+
689
+ - πŸ“§ Email: support@switch.org
690
+ - πŸ› Issues: [GitHub Issues](https://github.com/switch-org/analytics-tracker/issues)
691
+ - πŸ“– Documentation: See the [docs/](./docs) directory for comprehensive guides
692
+
693
+
694
+ --- -->
695
+
696
+ Made with ❀️ by ATIF RAFIQUE