@datalyr/web 1.0.7 → 1.1.1
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 +244 -48
- package/dist/attribution.d.ts +15 -2
- package/dist/attribution.d.ts.map +1 -1
- package/dist/auto-identify.d.ts +100 -0
- package/dist/auto-identify.d.ts.map +1 -0
- package/dist/container.d.ts +46 -10
- package/dist/container.d.ts.map +1 -1
- package/dist/datalyr.cjs.js +1460 -438
- package/dist/datalyr.cjs.js.map +1 -1
- package/dist/datalyr.esm.js +1460 -438
- package/dist/datalyr.esm.js.map +1 -1
- package/dist/datalyr.esm.min.js +1 -1
- package/dist/datalyr.esm.min.js.map +1 -1
- package/dist/datalyr.js +1460 -438
- package/dist/datalyr.js.map +1 -1
- package/dist/datalyr.min.js +1 -1
- package/dist/datalyr.min.js.map +1 -1
- package/dist/encryption.d.ts +63 -0
- package/dist/encryption.d.ts.map +1 -0
- package/dist/fingerprint.d.ts +6 -41
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/queue.d.ts +12 -0
- package/dist/queue.d.ts.map +1 -1
- package/dist/session.d.ts +16 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/storage.d.ts +31 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/types.d.ts +20 -37
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,76 @@ The official Datalyr Web SDK for browser-based analytics. Track user behavior, m
|
|
|
21
21
|
- 🔌 **Extensible** - Plugin system for custom functionality
|
|
22
22
|
- 📦 **Container Scripts** - Securely manage third-party pixels and tracking scripts
|
|
23
23
|
|
|
24
|
+
## Choosing the Right Installation Method
|
|
25
|
+
|
|
26
|
+
Datalyr offers two installation methods. Choose based on your needs:
|
|
27
|
+
|
|
28
|
+
### Script Tag (Recommended for Most Cases)
|
|
29
|
+
|
|
30
|
+
**Use when:** Simple setup, zero bundle size, no build step needed
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script defer src="https://track.datalyr.com/container.js"
|
|
34
|
+
data-workspace-id="YOUR_WORKSPACE_ID"></script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Pros:**
|
|
38
|
+
- ✅ Zero bundle size (external script)
|
|
39
|
+
- ✅ No npm install needed
|
|
40
|
+
- ✅ Works in any HTML page
|
|
41
|
+
- ✅ Automatic initialization
|
|
42
|
+
- ✅ Perfect for Next.js, React, Vue
|
|
43
|
+
|
|
44
|
+
**API Usage:**
|
|
45
|
+
```javascript
|
|
46
|
+
// Available globally after script loads
|
|
47
|
+
window.datalyr.track('event_name', { key: 'value' })
|
|
48
|
+
window.datalyr.identify('user_123', { email: 'user@example.com' })
|
|
49
|
+
window.datalyr.getVisitorId()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
[See Script Tag Documentation →](https://docs.datalyr.com/installation/script-tag)
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### NPM Package (For TypeScript/Bundlers)
|
|
57
|
+
|
|
58
|
+
**Use when:** You need TypeScript support, ES modules, or tree-shaking
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install @datalyr/web
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Pros:**
|
|
65
|
+
- ✅ Full TypeScript definitions
|
|
66
|
+
- ✅ ES module imports
|
|
67
|
+
- ✅ Tree-shaking support
|
|
68
|
+
- ✅ Better IDE autocomplete
|
|
69
|
+
|
|
70
|
+
**API Usage:**
|
|
71
|
+
```javascript
|
|
72
|
+
import datalyr from '@datalyr/web'
|
|
73
|
+
|
|
74
|
+
datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID' })
|
|
75
|
+
datalyr.track('event_name', { key: 'value' })
|
|
76
|
+
datalyr.identify('user_123', { email: 'user@example.com' })
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### ⚠️ Important: Don't Mix Both Methods
|
|
82
|
+
|
|
83
|
+
Choose **one** installation method. Using both can cause conflicts:
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<!-- ❌ DON'T DO THIS -->
|
|
87
|
+
<script src="https://track.datalyr.com/container.js"></script>
|
|
88
|
+
<script>
|
|
89
|
+
import datalyr from '@datalyr/web' // Both methods at once!
|
|
90
|
+
datalyr.init({...})
|
|
91
|
+
</script>
|
|
92
|
+
```
|
|
93
|
+
|
|
24
94
|
## Installation
|
|
25
95
|
|
|
26
96
|
```bash
|
|
@@ -65,19 +135,17 @@ import datalyr from '@datalyr/web';
|
|
|
65
135
|
// Initialize the SDK with configuration
|
|
66
136
|
datalyr.init({
|
|
67
137
|
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
68
|
-
|
|
138
|
+
|
|
69
139
|
// Optional configuration
|
|
70
|
-
// endpoint: 'https://custom.endpoint.com', // Optional: Custom endpoint
|
|
71
140
|
debug: false, // Enable debug logging
|
|
72
|
-
// batchSize: 10, // Optional: Events per batch
|
|
73
|
-
// flushInterval: 5000, // Optional: Flush interval (ms)
|
|
74
141
|
sessionTimeout: 1800000, // 30 minutes
|
|
75
142
|
privacyMode: 'standard', // 'standard' or 'strict'
|
|
76
143
|
respectDoNotTrack: false, // Honor browser DNT
|
|
77
144
|
respectGlobalPrivacyControl: true, // Honor GPC
|
|
78
145
|
cookieDomain: 'auto', // Auto-detect for subdomains
|
|
79
146
|
trackSPA: true, // Track SPA route changes
|
|
80
|
-
trackPageViews: true
|
|
147
|
+
trackPageViews: true, // Track initial page view
|
|
148
|
+
enableContainer: true // Enable container script loading
|
|
81
149
|
});
|
|
82
150
|
```
|
|
83
151
|
|
|
@@ -202,11 +270,11 @@ datalyr.setConsent({
|
|
|
202
270
|
// Get current session ID
|
|
203
271
|
const sessionId = datalyr.getSessionId();
|
|
204
272
|
|
|
205
|
-
// Manually end session
|
|
206
|
-
datalyr.endSession();
|
|
207
|
-
|
|
208
273
|
// Start new session
|
|
209
274
|
datalyr.startNewSession();
|
|
275
|
+
|
|
276
|
+
// Get session data
|
|
277
|
+
const sessionData = datalyr.getSessionData();
|
|
210
278
|
```
|
|
211
279
|
|
|
212
280
|
## Advanced Features
|
|
@@ -221,7 +289,7 @@ datalyr.track('Purchase', { value: 99.99 });
|
|
|
221
289
|
datalyr.track('Signup', { plan: 'premium' });
|
|
222
290
|
datalyr.track('Subscribe', { plan: 'annual' });
|
|
223
291
|
|
|
224
|
-
// E-commerce events
|
|
292
|
+
// E-commerce events
|
|
225
293
|
datalyr.track('Add to Cart', { product_id: 'SKU-123' });
|
|
226
294
|
datalyr.track('Begin Checkout', { cart_value: 199.99 });
|
|
227
295
|
```
|
|
@@ -365,19 +433,19 @@ import datalyr from '@datalyr/web';
|
|
|
365
433
|
// Create a plugin
|
|
366
434
|
const myPlugin = {
|
|
367
435
|
name: 'my-plugin',
|
|
368
|
-
|
|
436
|
+
|
|
369
437
|
initialize(datalyr) {
|
|
370
438
|
console.log('Plugin initialized');
|
|
371
439
|
},
|
|
372
|
-
|
|
440
|
+
|
|
373
441
|
track(eventName, properties) {
|
|
374
442
|
console.log('Event tracked:', eventName);
|
|
375
443
|
},
|
|
376
|
-
|
|
444
|
+
|
|
377
445
|
identify(userId, traits) {
|
|
378
446
|
console.log('User identified:', userId);
|
|
379
447
|
},
|
|
380
|
-
|
|
448
|
+
|
|
381
449
|
page(properties) {
|
|
382
450
|
console.log('Page tracked');
|
|
383
451
|
}
|
|
@@ -435,13 +503,13 @@ function App() {
|
|
|
435
503
|
workspaceId: 'YOUR_WORKSPACE_ID'
|
|
436
504
|
});
|
|
437
505
|
}, []);
|
|
438
|
-
|
|
506
|
+
|
|
439
507
|
const handleClick = () => {
|
|
440
508
|
datalyr.track('Button Clicked', {
|
|
441
509
|
button_name: 'CTA'
|
|
442
510
|
});
|
|
443
511
|
};
|
|
444
|
-
|
|
512
|
+
|
|
445
513
|
return <button onClick={handleClick}>Click Me</button>;
|
|
446
514
|
}
|
|
447
515
|
```
|
|
@@ -481,10 +549,19 @@ import datalyr from '@datalyr/web';
|
|
|
481
549
|
export function AnalyticsProvider({ children }) {
|
|
482
550
|
useEffect(() => {
|
|
483
551
|
datalyr.init({
|
|
484
|
-
workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID
|
|
552
|
+
workspaceId: process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID,
|
|
553
|
+
debug: process.env.NODE_ENV === 'development'
|
|
485
554
|
});
|
|
555
|
+
|
|
556
|
+
// Verify initialization
|
|
557
|
+
console.log('✅ Datalyr initialized');
|
|
558
|
+
console.log('Session ID:', datalyr.getSessionId());
|
|
559
|
+
console.log('Anonymous ID:', datalyr.getAnonymousId());
|
|
560
|
+
|
|
561
|
+
// Track initial page view
|
|
562
|
+
datalyr.page();
|
|
486
563
|
}, []);
|
|
487
|
-
|
|
564
|
+
|
|
488
565
|
return children;
|
|
489
566
|
}
|
|
490
567
|
|
|
@@ -516,13 +593,13 @@ When implementing authentication flows (login, signup, OAuth), you **MUST** call
|
|
|
516
593
|
// ❌ WRONG - Server-side identify breaks attribution
|
|
517
594
|
app.post('/api/login', async (req, res) => {
|
|
518
595
|
const user = await authenticate(email, password);
|
|
519
|
-
|
|
596
|
+
|
|
520
597
|
// This creates a NEW session without attribution data!
|
|
521
598
|
await serverAnalytics.identify({
|
|
522
599
|
userId: user.id,
|
|
523
600
|
traits: { email: user.email }
|
|
524
601
|
});
|
|
525
|
-
|
|
602
|
+
|
|
526
603
|
res.json({ userId: user.id });
|
|
527
604
|
});
|
|
528
605
|
// Result: Lost UTM params, fbclid, gclid, referrer - all gone!
|
|
@@ -540,16 +617,16 @@ function LoginPage() {
|
|
|
540
617
|
method: 'POST',
|
|
541
618
|
body: JSON.stringify({ email, password })
|
|
542
619
|
});
|
|
543
|
-
|
|
620
|
+
|
|
544
621
|
const { userId, email, name } = await response.json();
|
|
545
|
-
|
|
622
|
+
|
|
546
623
|
// 2. Identify in browser - this preserves attribution!
|
|
547
624
|
datalyr.identify(userId, {
|
|
548
625
|
email: email,
|
|
549
626
|
name: name,
|
|
550
627
|
login_method: 'email'
|
|
551
628
|
});
|
|
552
|
-
|
|
629
|
+
|
|
553
630
|
// 3. Then redirect
|
|
554
631
|
router.push('/dashboard');
|
|
555
632
|
};
|
|
@@ -558,7 +635,7 @@ function LoginPage() {
|
|
|
558
635
|
// Server endpoint - NO identify, just auth
|
|
559
636
|
app.post('/api/login', async (req, res) => {
|
|
560
637
|
const user = await authenticate(email, password);
|
|
561
|
-
|
|
638
|
+
|
|
562
639
|
// Just return user data for client-side identify
|
|
563
640
|
res.json({
|
|
564
641
|
userId: user.id,
|
|
@@ -605,7 +682,7 @@ useEffect(() => {
|
|
|
605
682
|
auth_method: 'google',
|
|
606
683
|
signup_source: localStorage.getItem('signup_source') || 'organic'
|
|
607
684
|
});
|
|
608
|
-
|
|
685
|
+
|
|
609
686
|
// Track the appropriate event
|
|
610
687
|
if (user.isNewUser) {
|
|
611
688
|
datalyr.track('Signup Completed', { method: 'oauth' });
|
|
@@ -619,9 +696,9 @@ useEffect(() => {
|
|
|
619
696
|
// Server-side: OAuth callback - NO identify!
|
|
620
697
|
export async function GET(request: Request) {
|
|
621
698
|
const { user } = await validateOAuthCallback(request);
|
|
622
|
-
|
|
699
|
+
|
|
623
700
|
const existingUser = await getUserByEmail(user.email);
|
|
624
|
-
|
|
701
|
+
|
|
625
702
|
if (existingUser) {
|
|
626
703
|
// Set session, but DON'T identify
|
|
627
704
|
await createSession(existingUser.id);
|
|
@@ -644,10 +721,10 @@ Use server-side for **tracking events** with userId (not identify):
|
|
|
644
721
|
// Stripe webhook example
|
|
645
722
|
app.post('/webhook/stripe', async (req, res) => {
|
|
646
723
|
const event = stripe.webhooks.constructEvent(req.body, sig, secret);
|
|
647
|
-
|
|
724
|
+
|
|
648
725
|
if (event.type === 'checkout.session.completed') {
|
|
649
726
|
const session = event.data.object;
|
|
650
|
-
|
|
727
|
+
|
|
651
728
|
// Track purchase event with userId (NOT identify)
|
|
652
729
|
await analytics.track({
|
|
653
730
|
userId: session.client_reference_id, // Your user ID
|
|
@@ -733,9 +810,9 @@ async function handleLogin(email, password) {
|
|
|
733
810
|
method: 'POST',
|
|
734
811
|
body: JSON.stringify({ email, password })
|
|
735
812
|
});
|
|
736
|
-
|
|
813
|
+
|
|
737
814
|
const user = await response.json();
|
|
738
|
-
|
|
815
|
+
|
|
739
816
|
// Identify in browser - preserves attribution!
|
|
740
817
|
datalyr.identify(user.id, {
|
|
741
818
|
email: user.email,
|
|
@@ -748,16 +825,14 @@ async function handleLogin(email, password) {
|
|
|
748
825
|
// This breaks attribution tracking!
|
|
749
826
|
app.post('/api/login', async (req, res) => {
|
|
750
827
|
const user = await authenticate(req.body);
|
|
751
|
-
|
|
828
|
+
|
|
752
829
|
// DON'T DO THIS - Creates new session
|
|
753
830
|
await serverAnalytics.identify(user.id, {
|
|
754
831
|
signup_date: new Date().toISOString()
|
|
755
832
|
});
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
});
|
|
760
|
-
}
|
|
833
|
+
|
|
834
|
+
res.json({ success: true });
|
|
835
|
+
});
|
|
761
836
|
```
|
|
762
837
|
|
|
763
838
|
### 5. Handle Errors Gracefully
|
|
@@ -775,31 +850,149 @@ window.addEventListener('error', (event) => {
|
|
|
775
850
|
});
|
|
776
851
|
```
|
|
777
852
|
|
|
853
|
+
## Troubleshooting
|
|
854
|
+
|
|
855
|
+
### Events Not Showing in Dashboard?
|
|
856
|
+
|
|
857
|
+
**Step 1: Verify SDK is initialized**
|
|
858
|
+
```javascript
|
|
859
|
+
import datalyr from '@datalyr/web'
|
|
860
|
+
|
|
861
|
+
// Check these return values
|
|
862
|
+
console.log('Anonymous ID:', datalyr.getAnonymousId()) // Should return "anon_xxxxx"
|
|
863
|
+
console.log('Session ID:', datalyr.getSessionId()) // Should return "sess_xxxxx"
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
If these return `undefined`, the SDK isn't initialized properly.
|
|
867
|
+
|
|
868
|
+
**Step 2: Check for errors**
|
|
869
|
+
```javascript
|
|
870
|
+
console.log('Errors:', datalyr.getErrors()) // Should be empty [] or show errors
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
**Step 3: Enable debug mode**
|
|
874
|
+
```javascript
|
|
875
|
+
datalyr.init({
|
|
876
|
+
workspaceId: 'YOUR_WORKSPACE_ID',
|
|
877
|
+
debug: true // Shows [Datalyr] logs in console
|
|
878
|
+
})
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
**Step 4: Verify network requests**
|
|
882
|
+
1. Open DevTools → Network tab
|
|
883
|
+
2. Filter by "ingest"
|
|
884
|
+
3. Track a test event: `datalyr.track('test', {})`
|
|
885
|
+
4. You should see POST to `ingest.datalyr.com`
|
|
886
|
+
|
|
887
|
+
### Common Errors
|
|
888
|
+
|
|
889
|
+
**"Cannot find module '@datalyr/web'"**
|
|
890
|
+
- Run `npm install @datalyr/web`
|
|
891
|
+
- Restart your dev server
|
|
892
|
+
|
|
893
|
+
**"process.env.NEXT_PUBLIC_DATALYR_WORKSPACE_ID is undefined"**
|
|
894
|
+
- Create `.env.local` file in project root
|
|
895
|
+
- Add: `NEXT_PUBLIC_DATALYR_WORKSPACE_ID=your_id`
|
|
896
|
+
- Restart dev server (required for env changes)
|
|
897
|
+
|
|
898
|
+
**"datalyr.getAnonymousId() returns undefined"**
|
|
899
|
+
- You're calling it during SSR (server-side rendering)
|
|
900
|
+
- Make sure you're in a Client Component with `'use client'`
|
|
901
|
+
- Call it inside `useEffect` or after component mounts
|
|
902
|
+
|
|
903
|
+
**Events tracked but not in dashboard**
|
|
904
|
+
- Verify workspace ID matches your dashboard
|
|
905
|
+
- Events may take 1-2 minutes to appear
|
|
906
|
+
- Check Network tab - requests should return 200 OK
|
|
907
|
+
- Check if ad blocker is enabled (disable it)
|
|
908
|
+
|
|
909
|
+
### Script Tag vs SDK Confusion
|
|
910
|
+
|
|
911
|
+
**If you're using the script tag:**
|
|
912
|
+
```javascript
|
|
913
|
+
// Check if loaded
|
|
914
|
+
window.datalyr._isLoaded // Should be true
|
|
915
|
+
window.datalyr.getVisitorId() // Should return ID
|
|
916
|
+
|
|
917
|
+
// Use object methods, not function calls
|
|
918
|
+
window.datalyr.track('event', {}) // ✅ Correct
|
|
919
|
+
window.datalyr("track", "event", {}) // ❌ Wrong
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
**If you're using the NPM SDK:**
|
|
923
|
+
```javascript
|
|
924
|
+
import datalyr from '@datalyr/web'
|
|
925
|
+
|
|
926
|
+
// Check if loaded
|
|
927
|
+
datalyr.getAnonymousId() // Should return ID
|
|
928
|
+
datalyr.getSessionId() // Should return ID
|
|
929
|
+
```
|
|
930
|
+
|
|
778
931
|
## Migration from Script Tag
|
|
779
932
|
|
|
780
|
-
If you're currently using the Datalyr script tag
|
|
933
|
+
If you're currently using the Datalyr script tag and want to switch to the NPM package:
|
|
781
934
|
|
|
782
935
|
### Before (Script Tag):
|
|
783
936
|
```html
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
937
|
+
<!-- In your HTML -->
|
|
938
|
+
<script defer src="https://track.datalyr.com/container.js"
|
|
939
|
+
data-workspace-id="YOUR_WORKSPACE_ID"></script>
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
```javascript
|
|
943
|
+
// In your JavaScript
|
|
944
|
+
window.datalyr.track('Button Clicked', {
|
|
945
|
+
button_name: 'Sign Up'
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
window.datalyr.identify('user_123', {
|
|
949
|
+
email: 'user@example.com'
|
|
950
|
+
});
|
|
790
951
|
```
|
|
791
952
|
|
|
792
|
-
### After (
|
|
953
|
+
### After (NPM SDK):
|
|
793
954
|
```javascript
|
|
955
|
+
// Remove the script tag from your HTML
|
|
956
|
+
|
|
957
|
+
// Install the package
|
|
958
|
+
// npm install @datalyr/web
|
|
959
|
+
|
|
960
|
+
// In your app initialization
|
|
794
961
|
import datalyr from '@datalyr/web';
|
|
795
962
|
|
|
796
963
|
datalyr.init({
|
|
797
964
|
workspaceId: 'YOUR_WORKSPACE_ID'
|
|
798
965
|
});
|
|
799
966
|
|
|
800
|
-
|
|
967
|
+
// In your components
|
|
968
|
+
datalyr.track('Button Clicked', {
|
|
969
|
+
button_name: 'Sign Up'
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
datalyr.identify('user_123', {
|
|
973
|
+
email: 'user@example.com'
|
|
974
|
+
});
|
|
801
975
|
```
|
|
802
976
|
|
|
977
|
+
**Note:** Most users should stay with the script tag unless they specifically need TypeScript support or ES module imports. The script tag has zero bundle size and works perfectly with Next.js, React, and Vue.
|
|
978
|
+
|
|
979
|
+
## API Comparison: Script Tag vs NPM SDK
|
|
980
|
+
|
|
981
|
+
| Feature | Script Tag | NPM SDK |
|
|
982
|
+
|---------|-----------|---------|
|
|
983
|
+
| **Installation** | Add `<script>` to HTML | `npm install @datalyr/web` |
|
|
984
|
+
| **Bundle Size** | 0 (external) | ~15KB |
|
|
985
|
+
| **TypeScript** | No types | ✅ Full types |
|
|
986
|
+
| **Import** | `window.datalyr` | `import datalyr from '@datalyr/web'` |
|
|
987
|
+
| **Initialize** | Automatic | `datalyr.init({...})` |
|
|
988
|
+
| **Track Event** | `window.datalyr.track(...)` | `datalyr.track(...)` |
|
|
989
|
+
| **Identify** | `window.datalyr.identify(...)` | `datalyr.identify(...)` |
|
|
990
|
+
| **Get Visitor ID** | `window.datalyr.getVisitorId()` | `datalyr.getAnonymousId()` |
|
|
991
|
+
| **Get Session** | `window.datalyr._getSessionId()` | `datalyr.getSessionId()` |
|
|
992
|
+
| **Check Loaded** | `window.datalyr._isLoaded` | `datalyr.getAnonymousId() !== undefined` |
|
|
993
|
+
|
|
994
|
+
Both methods use the same tracking backend and provide identical attribution tracking.
|
|
995
|
+
|
|
803
996
|
## Browser Support
|
|
804
997
|
|
|
805
998
|
The SDK supports all modern browsers:
|
|
@@ -823,7 +1016,7 @@ For older browsers, the SDK will gracefully degrade with fallback implementation
|
|
|
823
1016
|
The SDK is designed with privacy in mind:
|
|
824
1017
|
|
|
825
1018
|
- **GDPR Compliant**: Built-in consent management
|
|
826
|
-
- **CCPA Support**: "Do Not Sell" preference support
|
|
1019
|
+
- **CCPA Support**: "Do Not Sell" preference support
|
|
827
1020
|
- **Cookie Control**: Configurable cookie settings
|
|
828
1021
|
- **Data Minimization**: Collect only what you need
|
|
829
1022
|
- **User Rights**: Easy opt-out and data deletion
|
|
@@ -841,8 +1034,9 @@ datalyr.init({
|
|
|
841
1034
|
});
|
|
842
1035
|
|
|
843
1036
|
// Check console for detailed logs:
|
|
1037
|
+
// [Datalyr] SDK initialized
|
|
844
1038
|
// [Datalyr] Event tracked: Button Clicked
|
|
845
|
-
// [Datalyr]
|
|
1039
|
+
// [Datalyr] Batch sent successfully: 5 events
|
|
846
1040
|
```
|
|
847
1041
|
|
|
848
1042
|
## API Reference
|
|
@@ -883,6 +1077,8 @@ datalyr.init({
|
|
|
883
1077
|
| `getUserId()` | Get user ID |
|
|
884
1078
|
| `getDistinctId()` | Get distinct ID |
|
|
885
1079
|
| `getSessionId()` | Get session ID |
|
|
1080
|
+
| `getSessionData()` | Get session data |
|
|
1081
|
+
| `getErrors()` | Get SDK errors |
|
|
886
1082
|
| `loadScript(scriptId)` | Manually trigger a container script |
|
|
887
1083
|
| `getLoadedScripts()` | Get list of loaded container scripts |
|
|
888
1084
|
| `destroy()` | Clean up resources |
|
|
@@ -899,4 +1095,4 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
899
1095
|
|
|
900
1096
|
---
|
|
901
1097
|
|
|
902
|
-
Built with ❤️ by the Datalyr team
|
|
1098
|
+
Built with ❤️ by the Datalyr team
|
package/dist/attribution.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { Attribution, TouchPoint } from './types';
|
|
|
6
6
|
export declare class AttributionManager {
|
|
7
7
|
private attributionWindow;
|
|
8
8
|
private trackedParams;
|
|
9
|
+
private queryParamsCache;
|
|
9
10
|
private UTM_PARAMS;
|
|
10
11
|
private CLICK_IDS;
|
|
11
12
|
private DEFAULT_TRACKED_PARAMS;
|
|
@@ -13,24 +14,34 @@ export declare class AttributionManager {
|
|
|
13
14
|
attributionWindow?: number;
|
|
14
15
|
trackedParams?: string[];
|
|
15
16
|
});
|
|
17
|
+
/**
|
|
18
|
+
* Clear query params cache (called on page navigation)
|
|
19
|
+
* FIXED: Prevents stale attribution data on SPA navigation
|
|
20
|
+
*/
|
|
21
|
+
clearCache(): void;
|
|
16
22
|
/**
|
|
17
23
|
* Capture current attribution from URL
|
|
18
24
|
*/
|
|
19
25
|
captureAttribution(): Attribution;
|
|
20
26
|
/**
|
|
21
|
-
* Store first touch attribution
|
|
27
|
+
* Store first touch attribution with 90-day expiration
|
|
28
|
+
*
|
|
29
|
+
* FIXED (DATA-01): Removed paid priority logic that was corrupting first-touch attribution.
|
|
30
|
+
* First-touch is now IMMUTABLE except for expiration - this ensures accurate revenue attribution.
|
|
22
31
|
*/
|
|
23
32
|
storeFirstTouch(attribution: Attribution): void;
|
|
24
33
|
/**
|
|
25
34
|
* Get first touch attribution
|
|
35
|
+
* Checks expiry and removes if expired (Issue #4)
|
|
26
36
|
*/
|
|
27
37
|
getFirstTouch(): Attribution | null;
|
|
28
38
|
/**
|
|
29
|
-
* Store last touch attribution
|
|
39
|
+
* Store last touch attribution with 90-day expiration
|
|
30
40
|
*/
|
|
31
41
|
storeLastTouch(attribution: Attribution): void;
|
|
32
42
|
/**
|
|
33
43
|
* Get last touch attribution
|
|
44
|
+
* Checks expiry and removes if expired (Issue #4)
|
|
34
45
|
*/
|
|
35
46
|
getLastTouch(): Attribution | null;
|
|
36
47
|
/**
|
|
@@ -47,10 +58,12 @@ export declare class AttributionManager {
|
|
|
47
58
|
private captureAdCookies;
|
|
48
59
|
/**
|
|
49
60
|
* Check if we have a specific click ID in current params
|
|
61
|
+
* Uses cached params to avoid multiple URL parses (Issue #3)
|
|
50
62
|
*/
|
|
51
63
|
private hasClickId;
|
|
52
64
|
/**
|
|
53
65
|
* Get current fbclid from URL if present
|
|
66
|
+
* Uses cached params to avoid multiple URL parses (Issue #3)
|
|
54
67
|
*/
|
|
55
68
|
private getCurrentFbclid;
|
|
56
69
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEvD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,aAAa,CAAW;IAChC,OAAO,CAAC,UAAU,CAA2E;IAE7F,OAAO,CAAC,SAAS,CAgBf;IAEF,OAAO,CAAC,sBAAsB,CAO5B;gBAEU,OAAO,GAAE;QACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB;IAMN;;OAEG;IACH,kBAAkB,IAAI,WAAW;
|
|
1
|
+
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEvD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,aAAa,CAAW;IAChC,OAAO,CAAC,gBAAgB,CAAuC;IAC/D,OAAO,CAAC,UAAU,CAA2E;IAE7F,OAAO,CAAC,SAAS,CAgBf;IAEF,OAAO,CAAC,sBAAsB,CAO5B;gBAEU,OAAO,GAAE;QACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB;IAMN;;;OAGG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,kBAAkB,IAAI,WAAW;IA8DjC;;;;;OAKG;IACH,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAwB/C;;;OAGG;IACH,aAAa,IAAI,WAAW,GAAG,IAAI;IASnC;;OAEG;IACH,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAQ9C;;;OAGG;IACH,YAAY,IAAI,WAAW,GAAG,IAAI;IASlC;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,IAAI;IAqBhE;;OAEG;IACH,UAAU,IAAI,UAAU,EAAE;IAI1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA8CxB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAKlB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;OAEG;IACH,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IA0EzC;;OAEG;IACH,OAAO,CAAC,eAAe;IA6CvB;;OAEG;IACH,OAAO,CAAC,eAAe;IA2BvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB;;OAEG;IACH,oBAAoB,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO;IAKvD;;OAEG;IACH,uBAAuB,IAAI,IAAI;CAYhC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Identify Module
|
|
3
|
+
*
|
|
4
|
+
* Automatically captures user identity (email) from:
|
|
5
|
+
* 1. Fetch/XHR API requests (email in request/response)
|
|
6
|
+
* 2. Form submissions (email inputs)
|
|
7
|
+
* 3. Shopify-specific endpoints (/account.json)
|
|
8
|
+
*
|
|
9
|
+
* SECURITY:
|
|
10
|
+
* - Email validation to prevent false positives
|
|
11
|
+
* - Whitelist approach for trusted domains
|
|
12
|
+
* - Rate limiting to prevent spam
|
|
13
|
+
* - Privacy controls integration
|
|
14
|
+
*/
|
|
15
|
+
interface AutoIdentifyConfig {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
captureFromForms?: boolean;
|
|
18
|
+
captureFromAPI?: boolean;
|
|
19
|
+
captureFromShopify?: boolean;
|
|
20
|
+
trustedDomains?: string[];
|
|
21
|
+
debug?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare class AutoIdentifyManager {
|
|
24
|
+
private config;
|
|
25
|
+
private identifyCallback?;
|
|
26
|
+
private originalFetch?;
|
|
27
|
+
private originalXHROpen?;
|
|
28
|
+
private originalXHRSend?;
|
|
29
|
+
private formListeners;
|
|
30
|
+
private lastIdentifyTime;
|
|
31
|
+
private RATE_LIMIT_MS;
|
|
32
|
+
private shopifyCheckInterval?;
|
|
33
|
+
constructor(config?: AutoIdentifyConfig);
|
|
34
|
+
/**
|
|
35
|
+
* Initialize auto-identify system
|
|
36
|
+
*/
|
|
37
|
+
initialize(identifyCallback: (email: string, source: string) => void): void;
|
|
38
|
+
/**
|
|
39
|
+
* Setup form monitoring for email capture
|
|
40
|
+
*/
|
|
41
|
+
private setupFormMonitoring;
|
|
42
|
+
/**
|
|
43
|
+
* Scan DOM for forms with email inputs
|
|
44
|
+
*/
|
|
45
|
+
private scanForEmailForms;
|
|
46
|
+
/**
|
|
47
|
+
* Handle form submission
|
|
48
|
+
*/
|
|
49
|
+
private handleFormSubmit;
|
|
50
|
+
/**
|
|
51
|
+
* Setup fetch interception for API email capture
|
|
52
|
+
*/
|
|
53
|
+
private setupFetchInterception;
|
|
54
|
+
/**
|
|
55
|
+
* Setup XHR interception for API email capture
|
|
56
|
+
*/
|
|
57
|
+
private setupXHRInterception;
|
|
58
|
+
/**
|
|
59
|
+
* Setup Shopify-specific monitoring
|
|
60
|
+
*/
|
|
61
|
+
private setupShopifyMonitoring;
|
|
62
|
+
/**
|
|
63
|
+
* Check if running on Shopify
|
|
64
|
+
*/
|
|
65
|
+
private isShopify;
|
|
66
|
+
/**
|
|
67
|
+
* Check Shopify customer endpoint for email
|
|
68
|
+
*/
|
|
69
|
+
private checkShopifyCustomer;
|
|
70
|
+
/**
|
|
71
|
+
* Extract email from data (object, string, FormData)
|
|
72
|
+
*/
|
|
73
|
+
private extractEmailFromData;
|
|
74
|
+
/**
|
|
75
|
+
* Recursively find emails in object
|
|
76
|
+
*/
|
|
77
|
+
private findEmailsInObject;
|
|
78
|
+
/**
|
|
79
|
+
* Check if domain is trusted for auto-identify
|
|
80
|
+
*/
|
|
81
|
+
private isTrustedDomain;
|
|
82
|
+
/**
|
|
83
|
+
* Validate email format
|
|
84
|
+
*/
|
|
85
|
+
private isValidEmail;
|
|
86
|
+
/**
|
|
87
|
+
* Trigger identify with rate limiting
|
|
88
|
+
*/
|
|
89
|
+
private triggerIdentify;
|
|
90
|
+
/**
|
|
91
|
+
* Destroy and cleanup
|
|
92
|
+
*/
|
|
93
|
+
destroy(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Debug logging
|
|
96
|
+
*/
|
|
97
|
+
private log;
|
|
98
|
+
}
|
|
99
|
+
export {};
|
|
100
|
+
//# sourceMappingURL=auto-identify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-identify.d.ts","sourceRoot":"","sources":["../src/auto-identify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,gBAAgB,CAAC,CAA0C;IACnE,OAAO,CAAC,aAAa,CAAC,CAAsB;IAC5C,OAAO,CAAC,eAAe,CAAC,CAAuC;IAC/D,OAAO,CAAC,eAAe,CAAC,CAAuC;IAC/D,OAAO,CAAC,aAAa,CAA+D;IACpF,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,oBAAoB,CAAC,CAAiC;gBAElD,MAAM,GAAE,kBAAuB;IAW3C;;OAEG;IACH,UAAU,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAgC3E;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuBzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAgD9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA2C5B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAmB9B;;OAEG;IACH,OAAO,CAAC,SAAS;IAQjB;;OAEG;YACW,oBAAoB;IA0BlC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAwC5B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAuB1B;;OAEG;IACH,OAAO,CAAC,eAAe;IAuBvB;;OAEG;IACH,OAAO,CAAC,YAAY;IAwBpB;;OAEG;IACH,OAAO,CAAC,eAAe;IA0BvB;;OAEG;IACH,OAAO,IAAI,IAAI;IAiCf;;OAEG;IACH,OAAO,CAAC,GAAG;CAKZ"}
|