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/CHANGELOG.md +59 -0
- package/LICENSE +22 -0
- package/README.md +696 -0
- package/dist/index.cjs.js +1835 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.cts +415 -0
- package/dist/index.d.ts +415 -0
- package/dist/index.esm.js +1811 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +111 -0
package/README.md
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
# user-analytics-tracker
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/user-analytics-tracker)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](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
|