@datalyr/web 1.0.3 → 1.0.5

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 CHANGED
@@ -504,6 +504,128 @@ export default function RootLayout({ children }) {
504
504
  }
505
505
  ```
506
506
 
507
+ ## Authentication & Server-Side Tracking
508
+
509
+ ### ⚠️ Critical: Server-Side Identification for Authentication
510
+
511
+ When implementing authentication flows (login, signup, OAuth), you **MUST** call identify on the server-side before redirecting. Client-side identification during auth flows is unreliable and will cause attribution data loss.
512
+
513
+ #### The Problem
514
+
515
+ ```javascript
516
+ // ❌ WRONG - Client-side only (unreliable for auth flows)
517
+ function LoginPage() {
518
+ const handleLogin = async () => {
519
+ await login(email, password);
520
+ datalyr.identify(userId); // May not execute before redirect!
521
+ router.push('/dashboard'); // User redirects, identify never completes
522
+ };
523
+ }
524
+ ```
525
+
526
+ #### The Solution
527
+
528
+ ```javascript
529
+ // ✅ CORRECT - Server-side identification
530
+ // Next.js Server Action example
531
+ async function loginAction(email: string, password: string) {
532
+ const user = await authenticate(email, password);
533
+
534
+ // Critical: Identify BEFORE redirect
535
+ await fetch('https://datalyr.com/api/v1/events', {
536
+ method: 'POST',
537
+ headers: {
538
+ 'Authorization': `Bearer ${process.env.DATALYR_API_KEY}`,
539
+ 'Content-Type': 'application/json'
540
+ },
541
+ body: JSON.stringify({
542
+ event_name: '$identify',
543
+ user_id: user.id,
544
+ workspace_id: process.env.DATALYR_WORKSPACE_ID,
545
+ properties: {
546
+ email: user.email,
547
+ source: 'login'
548
+ }
549
+ })
550
+ });
551
+
552
+ redirect('/dashboard');
553
+ }
554
+
555
+ // For signup
556
+ async function signupAction(email: string, password: string) {
557
+ const user = await createUser(email, password);
558
+
559
+ // Identify new user server-side
560
+ await identifyUserServer(user.id, {
561
+ email: user.email,
562
+ created_at: new Date().toISOString(),
563
+ source: 'signup'
564
+ });
565
+
566
+ redirect('/onboarding');
567
+ }
568
+ ```
569
+
570
+ ### Why Server-Side Identification Matters
571
+
572
+ 1. **Attribution Tracking**: Links anonymous visitors who clicked ads (with fbclid, gclid, etc.) to authenticated users
573
+ 2. **Cross-Device Tracking**: Connects users across different devices and browsers
574
+ 3. **Reliability**: Guarantees execution before redirects, unlike client-side
575
+ 4. **OAuth Compatibility**: Works with complex OAuth redirect chains
576
+
577
+ ### Implementation Checklist
578
+
579
+ Ensure you call identify server-side in ALL these scenarios:
580
+
581
+ - [ ] **Email Signup**: Call identify when creating new accounts
582
+ - [ ] **Email Login**: Call identify for returning users
583
+ - [ ] **OAuth Signup**: Call identify in OAuth callback for new users
584
+ - [ ] **OAuth Login**: Call identify in OAuth callback for existing users
585
+ - [ ] **Magic Links**: Call identify when validating magic link tokens
586
+ - [ ] **Password Reset**: Call identify after successful password reset
587
+ - [ ] **Session Restore**: Call identify when validating existing sessions on app load
588
+
589
+ ### Example: Complete OAuth Implementation
590
+
591
+ ```javascript
592
+ // app/auth/callback/route.ts
593
+ export async function GET(request: Request) {
594
+ const { user } = await validateOAuthCallback(request);
595
+
596
+ const existingUser = await getUserByEmail(user.email);
597
+
598
+ if (existingUser) {
599
+ // Existing user logging in
600
+ await identifyUserServer(existingUser.id, {
601
+ email: existingUser.email,
602
+ source: 'oauth_login',
603
+ provider: 'google'
604
+ });
605
+ await trackEventServer('login', { method: 'oauth' });
606
+ } else {
607
+ // New user signing up
608
+ const newUser = await createUser(user);
609
+ await identifyUserServer(newUser.id, {
610
+ email: newUser.email,
611
+ source: 'oauth_signup',
612
+ provider: 'google'
613
+ });
614
+ await trackEventServer('signup', { method: 'oauth' });
615
+ }
616
+
617
+ return redirect('/dashboard');
618
+ }
619
+ ```
620
+
621
+ ### What Happens Without Server-Side Identify
622
+
623
+ Without proper server-side identification:
624
+ - **Lost Attribution**: Ad clicks (fbclid, gclid) won't be associated with conversions
625
+ - **Broken Cross-Device**: Users on multiple devices won't be linked
626
+ - **Incomplete Funnels**: User journeys will appear fragmented
627
+ - **Inaccurate Analytics**: Conversion rates and ROI calculations will be wrong
628
+
507
629
  ## Best Practices
508
630
 
509
631
  ### 1. Initialize Early
@@ -41,6 +41,18 @@ export declare class AttributionManager {
41
41
  * Get customer journey
42
42
  */
43
43
  getJourney(): TouchPoint[];
44
+ /**
45
+ * Capture advertising platform cookies
46
+ */
47
+ private captureAdCookies;
48
+ /**
49
+ * Check if we have a specific click ID in current params
50
+ */
51
+ private hasClickId;
52
+ /**
53
+ * Get current fbclid from URL if present
54
+ */
55
+ private getCurrentFbclid;
44
56
  /**
45
57
  * Get attribution data for event
46
58
  */
@@ -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;IAwDjC;;OAEG;IACH,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAU/C;;OAEG;IACH,aAAa,IAAI,WAAW,GAAG,IAAI;IAInC;;OAEG;IACH,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAO9C;;OAEG;IACH,YAAY,IAAI,WAAW,GAAG,IAAI;IAIlC;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,IAAI;IAqBhE;;OAEG;IACH,UAAU,IAAI,UAAU,EAAE;IAI1B;;OAEG;IACH,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAgDzC;;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"}
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;IAwDjC;;OAEG;IACH,eAAe,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAU/C;;OAEG;IACH,aAAa,IAAI,WAAW,GAAG,IAAI;IAInC;;OAEG;IACH,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI;IAO9C;;OAEG;IACH,YAAY,IAAI,WAAW,GAAG,IAAI;IAIlC;;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;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;OAEG;IACH,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAsDzC;;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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @datalyr/web v1.0.3
2
+ * @datalyr/web v1.0.5
3
3
  * Datalyr Web SDK - Modern attribution tracking for web applications
4
4
  * (c) 2025 Datalyr Inc.
5
5
  * Released under the MIT License
@@ -942,6 +942,59 @@ class AttributionManager {
942
942
  getJourney() {
943
943
  return storage.get('dl_journey', []);
944
944
  }
945
+ /**
946
+ * Capture advertising platform cookies
947
+ */
948
+ captureAdCookies() {
949
+ const adCookies = {};
950
+ // Facebook/Meta cookies
951
+ adCookies._fbp = cookies.get('_fbp');
952
+ adCookies._fbc = cookies.get('_fbc');
953
+ // Google Ads cookies
954
+ adCookies._gcl_aw = cookies.get('_gcl_aw');
955
+ adCookies._gcl_dc = cookies.get('_gcl_dc');
956
+ adCookies._gcl_gb = cookies.get('_gcl_gb');
957
+ adCookies._gcl_ha = cookies.get('_gcl_ha');
958
+ adCookies._gac = cookies.get('_gac');
959
+ // Google Analytics cookies
960
+ adCookies._ga = cookies.get('_ga');
961
+ adCookies._gid = cookies.get('_gid');
962
+ // TikTok cookies
963
+ adCookies._ttp = cookies.get('_ttp');
964
+ adCookies._ttc = cookies.get('_ttc');
965
+ // Generate _fbp if missing (Facebook browser ID)
966
+ if (!adCookies._fbp && (this.hasClickId('fbclid') || adCookies._fbc)) {
967
+ const timestamp = Date.now();
968
+ const randomId = Math.random().toString(36).substring(2, 15);
969
+ adCookies._fbp = `fb.1.${timestamp}.${randomId}`;
970
+ // Optionally set the cookie for future use
971
+ cookies.set('_fbp', adCookies._fbp, 90);
972
+ }
973
+ // Generate _fbc if we have fbclid but no _fbc
974
+ const fbclid = this.getCurrentFbclid();
975
+ if (fbclid && !adCookies._fbc) {
976
+ const timestamp = Math.floor(Date.now() / 1000);
977
+ adCookies._fbc = `fb.1.${timestamp}.${fbclid}`;
978
+ // Optionally set the cookie for future use
979
+ cookies.set('_fbc', adCookies._fbc, 90);
980
+ }
981
+ // Filter out null values for cleaner data
982
+ return Object.fromEntries(Object.entries(adCookies).filter(([_, value]) => value !== null));
983
+ }
984
+ /**
985
+ * Check if we have a specific click ID in current params
986
+ */
987
+ hasClickId(clickIdType) {
988
+ const params = getAllQueryParams();
989
+ return !!params[clickIdType];
990
+ }
991
+ /**
992
+ * Get current fbclid from URL if present
993
+ */
994
+ getCurrentFbclid() {
995
+ const params = getAllQueryParams();
996
+ return params.fbclid || null;
997
+ }
945
998
  /**
946
999
  * Get attribution data for event
947
1000
  */
@@ -950,6 +1003,8 @@ class AttributionManager {
950
1003
  const lastTouch = this.getLastTouch();
951
1004
  const journey = this.getJourney();
952
1005
  const current = this.captureAttribution();
1006
+ // Capture advertising cookies automatically
1007
+ const adCookies = this.captureAdCookies();
953
1008
  // Update first/last touch if needed
954
1009
  if (!firstTouch && Object.keys(current).length > 1) {
955
1010
  this.storeFirstTouch(current);
@@ -957,7 +1012,7 @@ class AttributionManager {
957
1012
  if (Object.keys(current).length > 1) {
958
1013
  this.storeLastTouch(current);
959
1014
  }
960
- return Object.assign(Object.assign({}, current), {
1015
+ return Object.assign(Object.assign(Object.assign({}, current), adCookies), {
961
1016
  // First touch (with snake_case aliases)
962
1017
  first_touch_source: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.source, first_touch_medium: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.medium, first_touch_campaign: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.campaign, first_touch_timestamp: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.timestamp, firstTouchSource: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.source, firstTouchMedium: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.medium, firstTouchCampaign: firstTouch === null || firstTouch === void 0 ? void 0 : firstTouch.campaign,
963
1018
  // Last touch (with snake_case aliases)