Skip to content

Implement attribution tracking and initialization across the application - #1164

Merged
LiorAgnin merged 6 commits into
qafrom
fix/analytics-attribution
Jan 7, 2026
Merged

Implement attribution tracking and initialization across the application#1164
LiorAgnin merged 6 commits into
qafrom
fix/analytics-attribution

Conversation

@LiorAgnin

Copy link
Copy Markdown
Contributor
  • Introduced useAttribution and useAttributionInitialization hooks for managing attribution data.
  • Integrated attribution tracking in various components and hooks, including signup, deposit, and KYC processes.
  • Removed the register.tsx file as part of the refactor.
  • Enhanced analytics tracking by enriching events with attribution data, including UTM parameters and referral codes.
  • Updated the track and trackIdentity functions to automatically include attribution context.
  • Added utility functions for parsing and managing attribution data from URLs and deep links.
  • Established a persistent store for attribution data using Zustand and MMKV for efficient state management.

@vercel

vercel Bot commented Jan 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Review Updated (UTC)
solid-app Ignored Ignored Preview Jan 7, 2026 2:52pm
solid-app-staging Ignored Ignored Preview Jan 7, 2026 2:52pm

@claude

claude Bot commented Jan 7, 2026

Copy link
Copy Markdown

Pull Request Review: Attribution Tracking Implementation

Overview

This PR introduces comprehensive attribution tracking across the application, capturing marketing attribution data (UTM parameters, referral codes, ad click IDs) and integrating it into analytics events. The implementation is well-structured with proper separation of concerns.


Strengths

1. Excellent Architecture & Separation of Concerns

  • Clean separation between core attribution logic (lib/attribution.ts), hooks (useAttribution.ts, useAttributionInitialization.ts), store (useAttributionStore.ts), and analytics integration
  • Well-documented code with comprehensive JSDoc comments
  • Clear naming conventions and type definitions

2. Comprehensive Attribution Coverage

  • Supports UTM parameters, advertising click IDs (Google, Facebook, Microsoft, TikTok), and referral codes
  • Multi-touch attribution tracking (first-touch and last-touch)
  • Platform-agnostic implementation (handles both web and mobile)

3. Security & Privacy

  • PII detection and sanitization in attribution values (lib/attribution.ts:365-382, store/useAttributionStore.ts:162-185)
  • Validates attribution data for completeness and quality
  • Click IDs are redacted in logs for privacy

4. Robust Error Handling

  • Comprehensive Sentry error tracking with proper context
  • Graceful degradation when attribution capture fails
  • Try-catch blocks around all critical operations

5. Good Testing Practices

  • Attribution validation with completeness scoring
  • Attribution channel categorization for reporting
  • Fingerprinting for deduplication

🔴 Critical Issues

1. Race Condition in Signup Flow ⚠️

Location: app/signup/creating.tsx:117-120

const _attributionHydrated = useAttributionStore(state => state._hasHydrated);
// ...
if (!_hasHydrated || !userStoreHydrated || !_attributionHydrated) return;

Issue: While the code waits for attribution hydration, there's a potential issue in the dependency array:

}, [_hasHydrated, userStoreHydrated, _attributionHydrated, users.length]);

If users.length changes before all stores hydrate, the effect will re-run and potentially skip the hydration check. Consider restructuring to ensure hydration completes before any other logic.

Suggestion:

useEffect(() => {
  // First effect: only handle hydration
  if (!_hasHydrated || !userStoreHydrated || !_attributionHydrated) return;
  
  // Set a flag when fully hydrated, then handle signup in a separate effect
  setIsFullyHydrated(true);
}, [_hasHydrated, userStoreHydrated, _attributionHydrated]);

useEffect(() => {
  if (!isFullyHydrated) return;
  // Handle signup logic here
}, [isFullyHydrated, users.length]);

2. Potential Memory Leak in Deep Link Listener

Location: hooks/useAttributionInitialization.ts:101-111

subscriptionRef.current = Linking.addEventListener('url', event => {
  console.warn('Deep link received while app running:', event.url);
  const captured = attributionStore.captureFromDeepLink(event.url);
  // ...
});

Issue: The deep link listener accesses attributionStore from the closure, which references the store at the time the effect ran. If the component re-renders due to store changes while the listener is active, this could cause stale closures.

Suggestion: Use useAttributionStore.getState() inside the listener callback instead of capturing it from the closure.

3. Missing Function Name in Welcome Screen

Location: app/welcome.tsx:80

onPress={() => handleSelectUserById(user.userId)}

Issue: The diff shows the function was renamed from handleSelectUser to handleSelectUserById, but I don't see this function definition in the provided diff. Need to verify this function exists in hooks/useUser.ts.


⚠️ High Priority Issues

1. Excessive Console Warnings in Production

Locations: Throughout codebase (e.g., hooks/useAttribution.ts:59, hooks/useAttributionInitialization.ts:37, store/useAttributionStore.ts:270)

Issue: The code uses console.warn() extensively for debugging, which will pollute production logs and potentially expose sensitive information.

Suggestion:

  • Replace with a proper logging service that respects environment
  • Use __DEV__ guards for debug logs
  • Consider a logging utility:
const logger = {
  debug: (message: string, data?: any) => {
    if (__DEV__) {
      console.log(message, data);
    }
  },
  // ...
};

2. Attribution Data Bloating Analytics Events

Location: lib/analytics.ts:214-228, lib/gtm.ts:182-210

Issue: Every tracked event now includes full attribution data (potentially 15-20 fields), which could:

  • Significantly increase analytics costs (Amplitude charges by event volume)
  • Make event schemas harder to maintain
  • Cause data quality issues if attribution data changes mid-session

Suggestion:

  • Consider storing attribution as a one-time user property instead of repeating on every event
  • Only include attribution on conversion events (signup, deposit, KYC)
  • Create a whitelist of events that need attribution vs. those that don't

3. Zustand Store Hydration Pattern

Location: store/useAttributionStore.ts:445-447

onRehydrateStorage: () => state => {
  state?.setHasHydrated(true);
},

Issue: The hydration callback doesn't handle errors. If MMKV fails to load, _hasHydrated will never be set to true, causing the app to hang.

Suggestion:

onRehydrateStorage: () => (state, error) => {
  if (error) {
    console.error('Failed to hydrate attribution store:', error);
    Sentry.captureException(error);
  }
  state?.setHasHydrated(true); // Set to true even on error to prevent blocking
},

4. Missing Attribution on Error Events

Location: components/ErrorBoundary.tsx:34-42

The RETRY_ATTEMPTED event doesn't include attribution data. For consistency, error events should also include attribution context.


💡 Medium Priority Issues

1. Unused Return Value in trackEventWithAttribution

Location: hooks/useAttribution.ts:183-200

const trackEventWithAttribution = useCallback(
  (eventName: string, params: Record<string, any> = {}) => {
    try {
      const attributionData = attributionStore.getAttributionForEvent();
      // This is a placeholder - the real implementation will be in analytics.ts
      console.warn(`Tracking event: ${eventName} with attribution:`, {
        ...params,
        ...attributionData,
        attribution_channel: attributionChannel,
      });
    }

Issue: This function appears to be a placeholder that's never actually used (the real tracking happens via analytics.ts directly). Consider either implementing it properly or removing it.

2. Inconsistent Attribution Capture Pattern

Location: Multiple files

Issue: Attribution is captured inconsistently:

  • Sometimes using useAttributionStore.getState().getAttributionForEvent() inside functions
  • Sometimes accessing store state directly via hooks
  • Sometimes calling getAttributionChannel() separately

Suggestion: Standardize on a single pattern:

const useAttributionForTracking = () => {
  const attributionData = useAttributionStore(state => state.getAttributionForEvent());
  const attributionChannel = useMemo(() => getAttributionChannel(attributionData), [attributionData]);
  return { attributionData, attributionChannel };
};

3. Attribution Window Not Configurable

Location: lib/attribution.ts:264-275, store/useAttributionStore.ts:431-439

Issue: The 30-day attribution window is hardcoded. Different campaigns might need different windows (7, 60, 90 days).

Suggestion: Make this configurable via environment variables or admin settings.

4. Referral Store Sync Could Cause Conflicts

Location: store/useAttributionStore.ts:273-277, 316-320

if (parsed.referral_code) {
  const referralStore = useReferralStore.getState();
  referralStore.setReferralCode(parsed.referral_code);
  console.warn('Referral code synced to referral store:', parsed.referral_code);
}

Issue: Syncing to two separate stores could cause data inconsistencies if one store updates but the other fails. Consider having a single source of truth.

5. Duplicate Parsing Logic

Location: lib/attribution.ts:12-73 and store/useAttributionStore.ts:90-135

Issue: URL parsing logic is duplicated between the lib file and the store file. The store should import and use the lib function.


🟡 Low Priority / Nice to Have

1. TypeScript Strictness

Location: store/useAttributionStore.ts:175

sanitized[key as keyof AttributionData] = value.trim() as any;

Using as any defeats TypeScript's type safety. Consider properly typing this.

2. Comment Removed Function

Location: app/welcome.tsx:80

The diff shows the function changed from handleSelectUser to handleSelectUserById. Verify the import and that this wasn't a breaking change.

3. Missing Tests

No test files were added for the new attribution functionality. Consider adding:

  • Unit tests for attribution parsing logic
  • Integration tests for multi-touch attribution
  • Mock tests for analytics enrichment

4. Magic Numbers

Location: Various

Values like 30138 (bridge wildcard) appear without explanation. Consider extracting to named constants:

const BRIDGE_WILDCARD = 30138; // Fuse chain ID for bridge routing

5. Error Messages Could Be More Specific

Location: hooks/useAttribution.ts:107-113

Generic error messages like "Failed to initialize attribution" could be more specific about what actually failed (URL parsing, store hydration, etc.).


🔒 Security Considerations

Good Practices:

  • PII sanitization is implemented
  • Click IDs are redacted in logs
  • Attribution data is validated before storage

⚠️ Recommendations:

  1. Rate Limiting: Consider rate-limiting attribution updates to prevent abuse
  2. Input Validation: Add maximum length validation for UTM parameters (currently only warns at 100 chars)
  3. XSS Prevention: Ensure attribution data is properly escaped when rendered in UI (if applicable)

📊 Performance Considerations

Potential Issues:

  1. Analytics Event Volume: Adding attribution to every event will increase Amplitude/Firebase costs
  2. Store Hydration Blocking: The signup flow blocks until attribution hydrates, which could slow first-time user experience
  3. Repeated Channel Calculation: getAttributionChannel() is called multiple times per event - consider memoization

Suggestions:

  1. Cache getAttributionChannel() result in the store
  2. Consider lazy-loading attribution for non-critical events
  3. Batch attribution updates to reduce store writes

🧪 Test Coverage

Missing:

  • Unit tests for attribution parsing
  • Integration tests for store hydration
  • E2E tests for signup flow with attribution
  • Mock tests for analytics enrichment

Recommended:

// Example test structure
describe('Attribution Store', () => {
  it('should parse UTM parameters from URL', () => {});
  it('should handle first-touch attribution correctly', () => {});
  it('should not overwrite first-touch data', () => {});
  it('should sanitize PII from attribution values', () => {});
  it('should handle store hydration errors gracefully', () => {});
});

📝 Documentation

Good:

  • Comprehensive inline comments
  • Clear JSDoc documentation
  • Type definitions are well-documented

🔄 Could Improve:

  • Add a high-level README explaining the attribution architecture
  • Document the attribution window policy
  • Create a guide for adding attribution to new events
  • Document the sync between referral store and attribution store

🎯 Recommendations Summary

Must Fix Before Merge:

  1. ✅ Fix potential race condition in signup flow hydration
  2. ✅ Fix deep link listener closure issue
  3. ✅ Verify handleSelectUserById function exists
  4. ✅ Add error handling to store hydration
  5. ✅ Reduce console.warn usage in production

Should Fix Soon:

  1. Deduplicate URL parsing logic
  2. Standardize attribution capture pattern
  3. Add attribution to error events for consistency
  4. Consider attribution data volume impact on analytics costs
  5. Add tests for critical paths

Nice to Have:

  1. Add comprehensive test coverage
  2. Create attribution documentation
  3. Make attribution window configurable
  4. Improve TypeScript strictness
  5. Add performance optimizations

🎉 Conclusion

This is a solid implementation of attribution tracking with good architecture, proper error handling, and comprehensive coverage. The main concerns are:

  1. Production readiness: Reduce logging noise and ensure error cases are handled
  2. Performance: Consider the impact of adding attribution to every analytics event
  3. Testing: Add test coverage before deploying to production

Overall Assessment:Approve with minor changes recommended

The code is production-ready after addressing the critical issues, particularly:

  • Store hydration error handling
  • Reducing console output
  • Verifying the handleSelectUserById function reference

Great work on this feature! The attribution tracking will provide valuable insights into user acquisition channels.

@claude

claude Bot commented Jan 7, 2026

Copy link
Copy Markdown

Pull Request Review: Attribution Tracking Implementation

Overview

This PR introduces a comprehensive attribution tracking system to capture and persist marketing attribution data (UTM parameters, referral codes, advertising click IDs) across web and mobile platforms. The implementation is well-architected with proper separation of concerns.

✅ Strengths

Architecture & Design

  • Excellent separation of concerns: Store (persistence), hooks (React integration), utilities (parsing/validation), and analytics integration are properly separated
  • Multi-touch attribution: First-touch and last-touch tracking with proper precedence rules (first-touch for source/campaign, last-touch for content/term)
  • Platform awareness: Handles web (URL parameters + referrer) and mobile (deep links) appropriately
  • Robust error handling: Comprehensive try-catch blocks with Sentry integration throughout
  • Type safety: Well-defined TypeScript interfaces for AttributionData with clear documentation

Code Quality

  • Excellent documentation: JSDoc comments explain complex logic clearly
  • Attribution validation: Smart validation with completeness scoring and helpful warnings
  • PII protection: Sanitization to prevent accidentally tracking emails/phone numbers
  • Hydration awareness: Proper Zustand hydration handling prevents race conditions

🔍 Issues & Recommendations

HIGH PRIORITY - Potential Bugs

1. Race Condition in signup/creating.tsx (Lines 117-118, 148-149)

// Three separate hydration checks can cause timing issues
if (!_hasHydrated || !userStoreHydrated || !_attributionHydrated) return;

Issue: If attribution store hydrates last, there's a brief window where signup could proceed without attribution.

Recommendation: Add a combined ready state or use Promise.all() to wait for all stores.

2. Missing Error Recovery in useAttributionInitialization.ts (Line 48)

hasInitialized.current = true; // Set immediately before async work

Issue: If initialization fails, the flag prevents retry attempts. Users who fail to capture initial attribution will never retry.

Recommendation:

try {
  // initialization logic
  hasInitialized.current = true; // Only set on success
} catch (error) {
  hasInitialized.current = false; // Allow retry
  Sentry.captureException(error);
}

3. Duplicate Attribution Parsing Logic

The same URL parsing logic exists in both:

  • lib/attribution.ts:parseAttributionFromURL()
  • store/useAttributionStore.ts:parseAttributionFromURL()

Issue: Two implementations can drift and cause inconsistencies. The lib version has .trim() on lines 27-31, but the store version doesn't (lines 98-102).

Recommendation: Remove duplication - have the store import and use the lib function.

MEDIUM PRIORITY - Code Quality

4. Inconsistent Console Logging

Throughout the codebase, console.warn() is used for informational messages (lines like "Attribution initialization complete"). This pollutes production logs.

Recommendation:

if (__DEV__) {
  console.log('Attribution captured:', data);
}

5. Attribution Channel Logic Could Be Simplified (lib/attribution.ts:291-359)

The getAttributionChannel() function has nested conditionals that are hard to follow.

Recommendation: Use a clearer waterfall pattern:

export const getAttributionChannel = (data: Partial<AttributionData>): AttributionChannel => {
  if (data.referral_code) return 'referral';
  if (data.gclid || data.msclkid) return 'paid_search';
  if (data.fbclid || data.ttclid) return 'paid_social';
  
  const medium = data.utm_medium?.toLowerCase();
  const source = data.utm_source?.toLowerCase();
  
  // ... continue with simplified logic
};

6. Memory Leak Risk in useAttributionInitialization (Lines 101-111)

The deep link listener is added but only cleaned up on unmount. If the effect runs multiple times (due to dependency changes), multiple listeners could accumulate.

Current code is OK since there's a guard (hasInitialized.current), but it's fragile.

Recommendation: Store the subscription in a ref and remove any existing subscription before adding a new one.

7. Removed File Without Migration (app/register.tsx deleted)

The PR deletes 249 lines from register.tsx without explanation. If this route is still referenced elsewhere, it will cause runtime errors.

Action Required: Verify all routes/links referencing /register have been updated or provide a redirect.

LOW PRIORITY - Performance & Best Practices

8. Unused Return Value in useAttribution.ts (Line 183-200)

The trackEventWithAttribution() function only logs to console, never actually tracks events. This is confusing API design.

Recommendation: Either implement it properly or remove it and document that users should use track() from analytics.ts directly.

9. Validation Result Not Used (useAttribution.ts:95-100)

Validation warnings are logged but not exposed to users or acted upon.

Recommendation: Consider exposing validation.warnings in the UI during testing/debugging, or at minimum track them as non-fatal Sentry breadcrumbs.

10. Magic Numbers (store/useAttributionStore.ts:431)

isAttributionExpired: (windowDays: number = 30) => {

The 30-day default is hardcoded in multiple places.

Recommendation: Extract to a named constant:

export const DEFAULT_ATTRIBUTION_WINDOW_DAYS = 30;

SECURITY CONSIDERATIONS

11. PII Sanitization Could Be More Robust (store/useAttributionStore.ts:166-168)

The regex pattern may not catch all PII variations (international phone numbers, emails with non-ASCII characters).

Recommendation: Consider using a dedicated PII detection library or more comprehensive patterns.

12. Attribution Data in Sentry (Multiple locations)

Attribution data (including potentially sensitive referral codes) is sent to Sentry in error contexts.

Recommendation: Review Sentry scrubbing rules to ensure no sensitive attribution data leaks.

📋 Testing Recommendations

This PR lacks test coverage. Critical areas needing tests:

  1. Unit tests for attribution parsing:

    • Test all UTM parameter combinations
    • Test advertising click ID parsing
    • Test referral code extraction (all parameter variations)
    • Test PII sanitization edge cases
  2. Integration tests:

    • Test first-touch vs last-touch precedence
    • Test hydration timing with multiple stores
    • Test deep link attribution on mobile
    • Test attribution expiration logic
  3. E2E tests:

    • Signup flow with attribution parameters
    • Deposit flow with attribution context
    • Deep link handling on mobile

Suggested test file structure:

__tests__/
  lib/attribution.test.ts
  store/useAttributionStore.test.ts
  hooks/useAttribution.test.ts
  hooks/useAttributionInitialization.test.ts

🎯 Action Items

Must Fix Before Merge

Should Fix Before Merge

Nice to Have

📊 Performance Impact

Minimal - The attribution tracking is lightweight:

  • MMKV storage is fast and efficient
  • Attribution capture happens once on app launch
  • No blocking network calls
  • Validation logic is O(1)

Recommendation: Monitor attribution store size over time. If users accumulate large attribution objects, consider pruning old data.

🔒 Security Assessment

Overall: Good with minor concerns:

🎓 Documentation Quality

Excellent - The code is well-documented with:

  • Clear JSDoc comments explaining complex logic
  • Inline comments for non-obvious decisions
  • TypeScript interfaces with field descriptions

Minor improvement: Add a README or docs/attribution.md explaining:

  • How attribution tracking works
  • Attribution windows and expiration
  • Multi-touch attribution model
  • How to test attribution locally

Final Recommendation

Approve with Changes - This is a solid implementation of a complex feature. The architecture is sound and the code quality is high. However, the bugs identified in Issues #1, #2, #3, and #7 should be fixed before merging to production.

The lack of tests is concerning for such a critical feature. I strongly recommend adding at least basic unit tests for the parsing and validation logic before merge.


Code Quality Score: 8/10
Test Coverage: 0/10 (no tests added)
Documentation: 9/10
Security: 8/10
Overall: 7.5/10

Great work on the implementation! The attribution system is well-designed and will provide valuable marketing insights. Address the critical bugs and add tests, and this will be production-ready.

@claude

claude Bot commented Jan 7, 2026

Copy link
Copy Markdown

PR Review: Attribution Tracking Implementation

Overview

This PR introduces a comprehensive attribution tracking system to capture marketing attribution data (UTM parameters, referral codes, click IDs) across web and mobile platforms. The implementation is well-structured with proper separation of concerns using hooks, stores, and utility functions.

✅ Strengths

Architecture & Design

  • Well-organized code structure: Separation between hooks (useAttribution, useAttributionInitialization), store (useAttributionStore), and utilities (lib/attribution.ts)
  • Platform-aware implementation: Properly handles web (URL params) vs mobile (deep links) differences
  • Multi-touch attribution: Supports both first-touch and last-touch attribution models, which is excellent for marketing analytics
  • Persistent storage: Uses MMKV for efficient storage with proper hydration handling

Code Quality

  • Comprehensive TypeScript types: Well-defined interfaces for AttributionData, AttributionValidationResult, etc.
  • Error handling: Extensive use of Sentry for error tracking and try-catch blocks
  • Documentation: Good inline comments and JSDoc explaining complex logic
  • Validation: PII detection to prevent accidentally tracking sensitive data (lines 365-382 in lib/attribution.ts)

Integration

  • Non-invasive integration: Attribution is captured automatically at app root without blocking user experience
  • Backward compatibility: Syncs referral codes to existing useReferralStore (lines 273-276 in store/useAttributionStore.ts)
  • Analytics enrichment: Properly integrates with existing track() and trackIdentity() functions

🐛 Critical Issues

1. Missing Import in ErrorBoundary.tsx

Location: components/ErrorBoundary.tsx:34

const handleRetry = useCallback(() => {

Issue: useCallback is used but not imported from React.

Fix:

import { useCallback, useEffect, useRef } from 'react';

Impact: This will cause a runtime error when the ErrorBoundary is rendered.

2. Excessive console.warn Usage

Locations: Throughout hooks/useAttribution.ts, hooks/useAttributionInitialization.ts, store/useAttributionStore.ts

Issue: Many console.warn() calls for normal operations (e.g., lines 59, 64, 71, 85, 91 in useAttributionInitialization.ts). These should be console.log() or removed in production.

Recommendation:

  • Use console.log() for informational messages
  • Reserve console.warn() for actual warnings
  • Consider using a proper logging library with log levels

3. Potential Race Condition in signup/creating.tsx

Location: app/signup/creating.tsx:117-120

const _attributionHydrated = useAttributionStore(state => state._hasHydrated);
// ...
if (!_hasHydrated || !userStoreHydrated || !_attributionHydrated) return;

Issue: While the PR correctly waits for attribution store hydration, there's a risk that if MMKV is slow, the user might experience signup delays.

Recommendation: Add a timeout fallback (e.g., 5 seconds) to proceed with signup even if attribution isn't hydrated, logging a warning instead.

⚠️ Important Issues

4. Duplicate Attribution Parsing Logic

Locations:

  • store/useAttributionStore.ts:90-135 (parseAttributionFromURL)
  • lib/attribution.ts:13-73 (parseAttributionFromURL)

Issue: The same URL parsing logic is duplicated in two files with slightly different implementations.

Recommendation: Remove the duplicate in the store file and import from lib/attribution.ts instead.

5. No Validation of Attribution Data Length

Location: lib/attribution.ts:184-190

While there's validation for suspiciously long values (>100 chars), the actual sanitization happens separately. Consider consolidating these validations.

6. Referrer Could Be Empty String

Location: hooks/useAttributionInitialization.ts:64-67

if (captured && referrer) {
  attributionStore.updateAttribution({
    landing_page_referrer: referrer,
  });
}

Issue: document.referrer can be an empty string, which is truthy. Should check for non-empty string: if (captured && referrer && referrer.trim())

7. Attribution Expiry Logic Edge Case

Location: lib/attribution.ts:264-275

if (!attributionData.first_visit_timestamp) return true;

Issue: Returns true (expired) when there's no timestamp, which might be misleading. Consider returning false or null to indicate "no attribution" rather than "expired attribution".

🔍 Code Quality Suggestions

8. Inconsistent Error Handling in trackEventWithAttribution

Location: hooks/useAttribution.ts:183-198

The function logs errors but doesn't throw them, which is fine, but it's just a placeholder that logs to console instead of actually calling track(). The comment says "This is a placeholder - the real implementation will be in analytics.ts" but this might confuse future developers.

9. Magic Numbers

Location: Multiple files

  • windowDays: number = 30 appears in several places (lines 224, 266, 431)
  • 2000 character limit for line truncation
  • 3600 seconds for permit deadline

Recommendation: Extract these to constants with descriptive names (e.g., DEFAULT_ATTRIBUTION_WINDOW_DAYS = 30).

10. Deep Link Listener Not Cleaned Up Properly on Error

Location: hooks/useAttributionInitialization.ts:127-136

If initialization fails with an error after setting up the listener but before the effect cleanup, the listener might leak.

Recommendation: Move the subscription setup inside a try-catch or ensure cleanup happens in a finally block.

🔒 Security Considerations

11. PII Detection Regex Could Be More Comprehensive

Location: lib/attribution.ts:372-374

The PII detection regex is good but could be more comprehensive:

  • Credit card numbers
  • IP addresses
  • API keys/tokens

Recommendation: Consider using a dedicated PII detection library or expanding the regex patterns.

12. URL Parsing Security

The code uses new URL() which can throw on malformed URLs. While this is caught, consider using a safer URL parsing approach for user-provided deep links.

📊 Performance Considerations

13. Store Selector Optimization

Location: app/signup/creating.tsx:117

const _attributionHydrated = useAttributionStore(state => state._hasHydrated);

Good use of selector for optimal re-renders.

14. Multiple Synchronous Reads from Store

Location: Various tracking calls

const attributionData = useAttributionStore.getState().getAttributionForEvent();
const attributionChannel = getAttributionChannel(attributionData);

This pattern is repeated many times. Consider memoizing or creating a helper hook.

🧪 Test Coverage

Major Gap: No unit tests for the new attribution functionality.

Recommendations:

  1. Add unit tests for parseAttributionFromURL with various URL formats
  2. Test PII detection in sanitizeAttribution
  3. Test multi-touch attribution merge logic
  4. Test attribution expiry calculations
  5. Mock MMKV storage for integration tests
  6. Add E2E tests for attribution capture flow

📝 Documentation

15. Missing Documentation

  • No README or docs explaining the attribution system architecture
  • No examples of how to use the attribution hooks in new components
  • No explanation of the attribution data model for the backend team

💡 Suggestions for Future Enhancements

  1. Attribution Window Configuration: Make the 30-day window configurable per campaign
  2. Attribution Reporting: Add a hook to generate attribution reports
  3. A/B Testing Integration: Layer A/B test variant tracking on top of attribution
  4. Server-Side Attribution: Consider server-side attribution enrichment for accuracy
  5. Attribution Visualization: Developer debugging UI to see current attribution state

🎯 Summary

This is a solid implementation of a complex feature. The architecture is sound, the code is well-structured, and the integration is thoughtful. However, there are some critical bugs that must be fixed before merging:

Must Fix Before Merge:

  1. Missing useCallback import in ErrorBoundary.tsx (breaks the app)
  2. 🔄 Duplicate parsing logic should be consolidated
  3. 📝 Excessive console.warn usage needs cleanup

Should Fix Before Merge:

  1. Race condition safety in signup flow
  2. Referrer empty string check
  3. Attribution expiry edge case handling

Nice to Have:

  1. Test coverage
  2. Documentation
  3. Extract magic numbers to constants
  4. Enhanced PII detection

Overall Assessment

Code Quality: ⭐⭐⭐⭐ (4/5)
Architecture: ⭐⭐⭐⭐⭐ (5/5)
Test Coverage: ⭐ (1/5)
Documentation: ⭐⭐ (2/5)

Recommendation: Request changes to fix the critical import bug and reduce console.warn usage before merging. The rest can be addressed in follow-up PRs, but the import bug is a blocker.


Great work on this feature! The multi-touch attribution model and comprehensive tracking will provide valuable insights into user acquisition and conversion funnels. 🚀

@claude

claude Bot commented Jan 7, 2026

Copy link
Copy Markdown

PR Review: Attribution Tracking Implementation

Overview

This PR implements a comprehensive attribution tracking system for marketing analytics, capturing UTM parameters, advertising click IDs, and referral codes throughout the user journey. The implementation is well-architected with clear separation of concerns.

✅ Strengths

Architecture & Design

  • Excellent separation of concerns: useAttribution (hook), lib/attribution.ts (utilities), and useAttributionStore (state management)
  • Multi-touch attribution: Properly tracks both first-touch and last-touch attribution, critical for accurate marketing ROI
  • Platform-agnostic: Clean handling of web (URL params) vs mobile (deep links) attribution capture
  • Persistent storage: Uses MMKV for performant cross-session attribution tracking
  • Automatic enrichment: All analytics events automatically include attribution context

Code Quality

  • Strong TypeScript usage: Well-defined interfaces (AttributionData, MultiTouchAttribution)
  • Comprehensive documentation: Excellent JSDoc comments throughout
  • Error handling: Proper try-catch blocks with Sentry error tracking
  • Sanitization: PII detection and removal to prevent accidental data leakage (lib/attribution.ts:365-382)
  • Channel classification: Smart attribution channel categorization for reporting

Integration

  • Centralized tracking: track() and trackIdentity() automatically enrich all events (lib/analytics.ts:194-242)
  • GTM integration: Proper dataLayer structure for Addressable and Google Ads conversion tracking
  • Backward compatibility: Syncs with existing useReferralStore (store/useAttributionStore.ts:274-276)

🐛 Potential Issues

1. Race Condition in Signup Flow (app/signup/creating.tsx:117-128)

if (!_hasHydrated || !userStoreHydrated || !_attributionHydrated) return;

Issue: The effect depends on three separate hydration flags. If stores hydrate in different orders, this could cause unexpected delays or missed attribution.

Recommendation: Consider using a single hydration coordinator or timeout fallback to prevent blocking the signup flow indefinitely.

2. Duplicate Console Logging

The codebase uses excessive console.warn() for attribution events. Examples:

  • hooks/useAttributionInitialization.ts:37,43,52,71,73,84,96
  • hooks/useAttribution.ts:59,64,84,99,105,126,156,190

Issue: In production, these logs could:

  • Expose attribution data in browser console
  • Create performance overhead
  • Make debugging harder due to noise

Recommendation: Use a proper logging service or wrap in __DEV__ checks:

if (__DEV__) {
  console.warn('Attribution captured:', formatAttributionForLogging(captured));
}

3. Error Swallowing in parseAttributionFromURL (lib/attribution.ts:64-72)

catch (error) {
  console.warn('Failed to parse attribution from URL:', error);
  Sentry.captureException(error, {...});
  return {};
}

Issue: Malformed URLs will silently fail and return empty object. This could hide integration issues.

Recommendation: Add basic URL validation before parsing:

if (!url || typeof url !== 'string') {
  console.warn('Invalid URL provided for attribution parsing');
  return {};
}

4. Missing Attribution in Error Boundary (components/ErrorBoundary.tsx:34-42)

The new retry tracking is great, but it doesn't include attribution data:

track(TRACKING_EVENTS.RETRY_ATTEMPTED, {
  error_name: error?.name,
  // Missing attribution context
});

Recommendation: Attribution is auto-enriched by track(), so this is fine. However, consider documenting this behavior.

5. Potential Memory Leak (hooks/useAttributionInitialization.ts:101-111)

subscriptionRef.current = Linking.addEventListener('url', event => {
  // Handler
});

Issue: The deep link listener is created on every hydration but cleanup only happens on unmount. If the effect re-runs (React Strict Mode), you could create multiple listeners.

Recommendation: Already handled correctly with cleanup function. Good work!


⚡ Performance Considerations

1. Synchronous Store Reads in Hot Paths

Multiple places call useAttributionStore.getState().getAttributionForEvent() synchronously during time-critical operations (deposit submission, signup).

Impact: Minimal - MMKV is synchronous but very fast. However, this adds ~1-5ms per call.

Recommendation: Consider memoizing attribution data at the component level if you notice performance issues:

const attributionData = useMemo(
  () => useAttributionStore.getState().getAttributionForEvent(),
  [] // Only capture once per mount
);

2. Validation on Every Event (lib/analytics.ts:209)

const attributionData = attributionStore.getAttributionForEvent();

This runs on every track() call. For high-frequency events, this could add overhead.

Recommendation: Currently acceptable. Monitor performance in production analytics.


🔒 Security Concerns

1. PII Detection Could Be Bypassed (lib/attribution.ts:365-382)

const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
const phonePattern = /(\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/;

Issue: Simple obfuscation (e.g., "user@example.com" → "user AT example DOT com") would bypass detection.

Recommendation: Current implementation is reasonable for basic protection. Document that this is defense-in-depth, not a primary security control.

2. Attribution Data in GTM dataLayer (lib/gtm.ts:136-150)

GTM dataLayer is accessible to all GTM tags and triggers. Third-party tags could potentially access attribution data.

Recommendation:

  • Audit all GTM tags to ensure they're from trusted vendors
  • Consider excluding sensitive fields from GTM if not needed
  • Document which attribution fields are exposed to GTM

3. Referral Code Syncing (store/useAttributionStore.ts:274-276)

if (parsed.referral_code) {
  const referralStore = useReferralStore.getState();
  referralStore.setReferralCode(parsed.referral_code);
}

Issue: Direct store-to-store coupling. If useReferralStore has different validation rules, this could bypass them.

Recommendation: Use useReferralStore's public API methods if they exist, or document this as intended behavior.


🧪 Test Coverage

Critical Gap: No unit tests for attribution logic.

Missing Test Coverage:

  1. ✅ Attribution parsing from various URL formats
  2. ✅ PII detection and sanitization
  3. ✅ First-touch vs last-touch attribution precedence
  4. ✅ Deep link attribution capture
  5. ✅ Attribution expiration logic
  6. ✅ Channel classification accuracy

Recommendation: Add tests for critical paths:

// Example test structure
describe('Attribution', () => {
  describe('parseAttributionFromURL', () => {
    it('should parse standard UTM parameters', () => {
      const result = parseAttributionFromURL('https://app.solid.xyz?utm_source=google&utm_campaign=summer');
      expect(result.utm_source).toBe('google');
      expect(result.utm_campaign).toBe('summer');
    });
    
    it('should detect and remove PII from attribution', () => {
      const result = parseAttributionFromURL('https://app.solid.xyz?utm_source=user@example.com');
      expect(result.utm_source).toBeUndefined();
    });
    
    it('should handle malformed URLs gracefully', () => {
      expect(() => parseAttributionFromURL('not-a-url')).not.toThrow();
    });
  });
  
  describe('getAttributionChannel', () => {
    it('should correctly classify referral traffic', () => {
      const channel = getAttributionChannel({ referral_code: 'FRIEND123' });
      expect(channel).toBe('referral');
    });
    
    it('should correctly classify paid search', () => {
      const channel = getAttributionChannel({ gclid: 'abc123', utm_source: 'google' });
      expect(channel).toBe('paid_search');
    });
  });
});

📝 Best Practices & Recommendations

1. Remove Deleted File from Routing

The PR deletes app/register.tsx but I don't see routing updates. Verify that all routes referencing /register are updated.

Check: Search for references to register route in navigation/routing code.

2. Attribution Window Configuration

The 30-day attribution window is hardcoded (store/useAttributionStore.ts:431).

Recommendation: Make this configurable:

export const ATTRIBUTION_WINDOW_DAYS = EXPO_PUBLIC_ATTRIBUTION_WINDOW_DAYS || 30;

3. Documentation

Consider adding:

  • Attribution data flow diagram
  • Examples of how to query attribution in Amplitude/GTM
  • Migration guide for teams relying on old attribution logic

4. Monitoring & Alerting

Set up monitoring for:

  • Attribution capture rate (% of signups with attribution)
  • Attribution completeness score
  • PII detection frequency (should be near 0%)
  • Attribution validation warnings

🎯 Summary

Recommendation: ✅ Approve with Minor Improvements

This is a well-designed, production-ready attribution system. The code quality is high, the architecture is sound, and the integration is comprehensive.

Priority Fixes:

  1. 🔴 HIGH: Add defensive checks around hydration race conditions in signup flow
  2. 🟡 MEDIUM: Reduce console.warn() spam in production
  3. 🟡 MEDIUM: Add basic unit tests for critical attribution utilities
  4. 🟢 LOW: Document PII sanitization limitations
  5. 🟢 LOW: Consider making attribution window configurable

Impact: This PR will significantly improve marketing attribution visibility and enable accurate ROI measurement for user acquisition campaigns.

Great work on this implementation! 🚀

@LiorAgnin
LiorAgnin force-pushed the fix/analytics-attribution branch from 1304916 to 0d455e7 Compare January 7, 2026 13:29
@claude

claude Bot commented Jan 7, 2026

Copy link
Copy Markdown

Pull Request Review: Attribution Tracking Implementation

Summary

This PR implements a comprehensive attribution tracking system for marketing analytics. The implementation adds UTM parameter tracking, referral code management, and advertising click ID capture across web and mobile platforms. Overall, this is a well-structured and thoughtfully designed implementation with good separation of concerns.


✅ Strengths

1. Architecture & Code Quality

  • Excellent separation of concerns: Attribution logic is cleanly separated into hooks (useAttribution, useAttributionInitialization), utilities (lib/attribution.ts), and state management (useAttributionStore)
  • Multi-touch attribution support: Properly tracks both first-touch and last-touch attribution, which is critical for accurate marketing analytics
  • Type safety: Good use of TypeScript interfaces (e.g., AttributionData, AttributionChannel) throughout
  • Platform-agnostic design: Handles both web (URL params) and mobile (deep links) attribution capture with appropriate platform checks

2. Robust Error Handling

  • Comprehensive Sentry integration for error tracking in all critical paths
  • Graceful degradation when analytics fails - doesn't block user experience
  • PII detection to prevent accidentally tracking sensitive data (lib/attribution.ts:365-382)

3. Data Persistence

  • Smart use of MMKV storage through Zustand persistence for fast, efficient storage
  • Proper hydration handling to avoid race conditions (useAttributionInitialization.ts:36-39)
  • First-touch attribution is immutable once set, preserving original attribution (useAttributionStore.ts:339-356)

4. Analytics Integration

  • Attribution data is automatically enriched in all track() and trackIdentity() calls (analytics.ts:195-242, 348-407)
  • Amplitude device ID bridging for anonymous-to-identified user tracking
  • Multi-provider support (Amplitude, Firebase, GTM)

🔴 Critical Issues

1. Race Condition in Signup Flow ⚠️

Location: app/signup/creating.tsx:117-127

useEffect(() => {
  if (!_hasHydrated || !userStoreHydrated || !_attributionHydrated) return;
  // ...
}, [_hasHydrated, userStoreHydrated, _attributionHydrated, users.length]);

Issue: The dependency array includes users.length, which can trigger the effect multiple times. If a user navigates back and forth, this could cause duplicate createAccount() calls despite the isCreatingRef guard.

Recommendation:

  • Remove users.length from the dependency array if the guard is sufficient
  • Or add more explicit state to track if account creation has completed

2. Missing Input Validation ⚠️

Location: store/useAttributionStore.ts:90-135

The parseAttributionFromURL function extracts parameters without length validation:

if (params.get('utm_source')) attribution.utm_source = params.get('utm_source')!;

Issue: Malicious or malformed URLs could inject extremely long strings, potentially causing storage issues or analytics ingestion problems.

Recommendation: Add length limits:

const maxLength = 200;
const utmSource = params.get('utm_source');
if (utmSource && utmSource.length <= maxLength) {
  attribution.utm_source = utmSource.trim();
}

3. Concurrent Modification Risk

Location: lib/analytics.ts:195-242

const attributionStore = useAttributionStore.getState();
const attributionData = attributionStore.getAttributionForEvent();

Issue: If attribution data is being updated concurrently (e.g., a deep link arrives while tracking an event), there could be a race condition where partial data is captured.

Recommendation: Consider taking a snapshot of attribution data at critical conversion points (signup, deposit) rather than reading on-demand during tracking.


🟡 Important Issues

4. Console Warnings in Production

Location: Multiple files use console.warn extensively

console.warn('Attribution captured on web:', formatAttributionForLogging(captured));

Issue: These warnings will appear in production browser consoles and may concern users or reveal internal implementation details.

Recommendation:

  • Use console.debug() for non-critical logs
  • Gate verbose logging behind a feature flag
  • Or use a logging library that automatically strips debug logs in production

5. Performance: Excessive Logging

Location: hooks/useAttributionInitialization.ts, store/useAttributionStore.ts

Multiple console.warn calls on every app launch and attribution capture.

Recommendation:

  • Reduce logging verbosity in production
  • Consider structured logging with log levels

6. PII Pattern Too Narrow

Location: store/useAttributionStore.ts:165-178, lib/attribution.ts:365-382

The PII regex only catches basic email, phone, and SSN patterns:

const piiPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}|.../;

Issue: Could miss credit card numbers, API keys, JWT tokens, etc.

Recommendation:

  • Add patterns for credit cards (Luhn algorithm), API keys, tokens
  • Consider using a dedicated PII detection library
  • Add unit tests for PII detection edge cases

🟢 Minor Issues & Suggestions

7. Inconsistent Null Checks

Location: lib/attribution.ts:104-116

export const getCurrentURL = (): string | null => {
  if (Platform.OS !== 'web') return null;
  
  try {
    if (typeof window !== 'undefined' && window.location) {
      return window.location.href;
    }
  } catch (error) {
    console.warn('Failed to get current URL:', error);
  }
  
  return null;
}

Suggestion: The try-catch is good, but you could simplify:

if (Platform.OS !== 'web' || typeof window === 'undefined') return null;
return window.location?.href ?? null;

8. Hardcoded Attribution Window

Location: store/useAttributionStore.ts:431-439

isAttributionExpired: (windowDays: number = 30) => {

Suggestion: Consider making the default window configurable via environment variable or config file for easier A/B testing of attribution windows.

9. Missing TypeScript Strict Checks

Location: Various files using ! non-null assertions

attribution.utm_source = params.get('utm_source')!;

Suggestion: The logic already checks for truthiness, but using ! bypasses TypeScript safety. Consider:

const source = params.get('utm_source');
if (source) attribution.utm_source = source;

10. Event Name Typos Risk

Location: Throughout the codebase, event names are strings

track(TRACKING_EVENTS.DEPOSIT_BANK_AMOUNT_VIEWED, {...});

Observation: This is good - you're using constants! But consider adding runtime validation or TypeScript const assertions to ensure event names match expected patterns.


🔒 Security Concerns

✅ Good Security Practices:

  1. PII sanitization is implemented (though could be improved - see Wrap RootLayout in SafeAreaProvider for improved layout handling #6)
  2. No sensitive data in URLs - referral codes and UTM params are safe
  3. Sentry integration properly tags errors without exposing sensitive data
  4. Click IDs are masked in logs (lib/attribution.ts:394-395)

⚠️ Areas for Improvement:

  1. Input validation - Add length limits on all URL parameters (see design card screens #2)
  2. SSRF protection - If you ever validate/fetch referrer URLs, add domain allowlisting
  3. XSS protection - The data isn't rendered directly, but ensure downstream consumers sanitize if displaying

🧪 Test Coverage

Critical Gap: No Unit Tests

There are no test files for the new attribution functionality. This is a significant risk given the complexity.

Recommendation: Add tests for:

  1. lib/attribution.ts:

    • parseAttributionFromURL() with various URL formats
    • sanitizeAttributionValue() with PII patterns
    • getAttributionChannel() with different UTM combinations
    • Edge cases: malformed URLs, extremely long params, special characters
  2. store/useAttributionStore.ts:

    • First-touch immutability (attempting to overwrite should fail)
    • Last-touch updates
    • Multi-touch merging logic
    • Attribution expiration calculations
  3. hooks/useAttribution.ts:

    • Initialization flows (web vs mobile)
    • Deep link handling
    • Concurrent updates

Suggested test file structure:

__tests__/
  lib/
    attribution.test.ts
  store/
    useAttributionStore.test.ts
  hooks/
    useAttribution.test.ts
    useAttributionInitialization.test.ts

📊 Performance Considerations

✅ Good Performance:

  1. MMKV storage is extremely fast (native key-value store)
  2. Zustand is lightweight and performant
  3. Parallel analytics tracking using Promise.allSettled() (analytics.ts:234-238)
  4. Lazy initialization - attribution only captured when needed

⚠️ Potential Concerns:

  1. Multiple store reads per event: Every track() call reads from the store. Consider memoization if tracking is very frequent.
  2. Synchronous URL parsing: new URL() is generally fast but could be wrapped in a try-catch timeout for safety
  3. Deep link listener never removed: In useAttributionInitialization.ts, the listener is only removed on unmount. If the hook re-runs, you might create duplicate listeners.

Recommendation for #3:

// Store subscription ref to prevent duplicates
if (subscriptionRef.current) {
  subscriptionRef.current.remove();
}
subscriptionRef.current = Linking.addEventListener('url', ...);

🎯 Additional Recommendations

1. Add Attribution Dashboard/Debug Tool

For QA and debugging, consider adding a dev-only screen that displays:

  • Current attribution data
  • Validation status
  • Attribution age
  • Whether it's expired

2. Add Attribution Reset for Testing

Add a dev-only function to clear attribution data for testing different attribution scenarios.

3. Document Attribution Model

Create documentation (README or wiki) explaining:

  • How first-touch vs last-touch works
  • Attribution window policy (30 days default)
  • What happens when attribution expires
  • How referral codes interact with UTM params

4. Monitor Attribution Capture Rate

Add metrics to track:

  • % of sessions with attribution data
  • % of signups with attribution
  • Most common attribution channels

5. Consider GDPR/Privacy Compliance

Ensure this tracking complies with:

  • GDPR (EU)
  • CCPA (California)
  • Other privacy regulations

You may need:

  • User consent before capturing attribution
  • Data retention policies
  • Right to deletion

Summary & Recommendation

Overall Assessment: ✅ Approve with changes requested

This is a well-architected implementation with good separation of concerns and robust error handling. The code quality is high, and the approach to multi-touch attribution is sound.

Before merging:

  1. Must fix: Add input validation and length limits (design card screens #2)
  2. Must fix: Reduce console.warn usage in production (Add Deposit screen #4)
  3. Should fix: Review signup race condition (configure project for Android #1)
  4. ⚠️ Strongly recommended: Add unit tests for core attribution logic
  5. ⚠️ Recommended: Improve PII detection (Wrap RootLayout in SafeAreaProvider for improved layout handling #6)

After merging:

  • Add comprehensive test coverage
  • Monitor attribution capture rates in production
  • Document the attribution model for the team

Great work on this implementation! The architecture is solid and sets a good foundation for marketing analytics.


Review completed by Claude Code 🤖

- Introduced `useAttribution` and `useAttributionInitialization` hooks for managing attribution data.
- Integrated attribution tracking in various components and hooks, including signup, deposit, and KYC processes.
- Removed the `register.tsx` file as part of the refactor.
- Enhanced analytics tracking by enriching events with attribution data, including UTM parameters and referral codes.
- Updated the `track` and `trackIdentity` functions to automatically include attribution context.
- Added utility functions for parsing and managing attribution data from URLs and deep links.
- Established a persistent store for attribution data using Zustand and MMKV for efficient state management.
- Updated handleRetry to utilize useCallback for better performance and to prevent unnecessary re-renders.
- Added dependencies to the useCallback hook to ensure it has access to the latest error and retry props.
- Replaced the import of `useEffect` with `useCallback` to optimize the component's performance.
- Ensured the `path` import is correctly positioned for better code organization.
- Implemented comprehensive tracking for user interactions in the deposit flow, including viewing and selecting deposit methods, form submissions, and session management.
- Added tracking for KYC steps and user actions in the KYC process to improve analytics insights.
- Refactored components to utilize hooks for tracking events, ensuring consistent and efficient data collection.
- Enhanced error handling and user feedback mechanisms in deposit-related components.
- Updated tracking events in the constants file to reflect new analytics requirements.
- Remove /register route from app layout and path constants
- Fix referral code preservation during signup flow reset
- Add hydration tracking to referral store to prevent race conditions
- Improve attribution initialization to wait for both stores
- Add referral code sync verification with Sentry error tracking
- Implement multi-source fallback for referral code (URL > referral > attribution)
@LiorAgnin
LiorAgnin force-pushed the fix/analytics-attribution branch from bfa2b67 to f487b6c Compare January 7, 2026 14:52
@claude

claude Bot commented Jan 7, 2026

Copy link
Copy Markdown

PR Review: Attribution Tracking Implementation

🎯 Overview

This PR implements a comprehensive attribution tracking system across the application. The implementation is well-structured with good separation of concerns. I've identified several areas for improvement around security, error handling, and best practices.


✅ Strengths

Architecture & Design

  • Excellent separation of concerns: Attribution logic split into hooks/, lib/, and store/ follows clean architecture
  • Multi-touch attribution support: Properly tracks both first-touch and last-touch attribution
  • Platform-agnostic design: Handles web (URL params) and mobile (deep links) seamlessly
  • Zustand + MMKV persistence: Good choice for cross-platform state management with persistence

Implementation Quality

  • Comprehensive UTM tracking: Captures all standard marketing parameters (source, medium, campaign, content, term)
  • Advertising platform support: Handles click IDs from Google, Facebook, Microsoft, TikTok
  • Proper hydration handling: Waits for store hydration before initialization to prevent race conditions
  • Analytics enrichment: Automatically enriches all events with attribution context

🚨 Critical Issues

1. Security: PII Detection Pattern Incomplete

Location: store/useAttributionStore.ts:166-168, lib/attribution.ts:365-382

The PII detection regex only checks for emails, phones, and SSNs. Missing common PII patterns:

// Current (incomplete)
const piiPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}|(\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/;

// Recommended: Add credit cards, IP addresses, etc.
const creditCardPattern = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/;
const ipPattern = /\b(?:\d{1,3}\.){3}\d{1,3}\b/;

Impact: High - Could inadvertently track/store PII in analytics
Recommendation: Expand PII detection or use a dedicated library like validator.js

2. Race Condition in Initialization

Location: hooks/useAttributionInitialization.ts:35-52

The hasInitialized.current flag is set before async work completes:

hasInitialized.current = true; // Line 51 - set BEFORE initializeAttribution() completes

const initializeAttribution = async () => {
  // async work here...
}

Impact: Medium - Could allow multiple initialization attempts in fast navigation
Recommendation: Set flag after successful initialization or use a state machine

3. Silent Error Swallowing

Location: Multiple files (lib/analytics.ts:234-238, hooks/useAttribution.ts:106-113)

Errors are logged but don't prevent execution, potentially masking issues:

Promise.allSettled([...]) // Continues even if all providers fail

Impact: Medium - Analytics failures invisible to monitoring
Recommendation:

  • Add error tracking to Sentry with proper severity levels
  • Consider circuit breaker pattern for repeated failures
  • Add success/failure metrics

⚠️ Important Issues

4. Missing Input Validation

Location: lib/attribution.ts:14-72, store/useAttributionStore.ts:90-134

URL parsing doesn't validate parameter values:

if (params.get('utm_source')) attribution.utm_source = params.get('utm_source')!;

Issues:

  • No length limits (could cause storage issues)
  • No character validation (special characters, XSS attempts)
  • Accepts any string value

Recommendation:

const validateParam = (value: string, maxLength = 100): string | null => {
  if (!value || value.length > maxLength) return null;
  // Remove potentially dangerous characters
  return value.replace(/[<>\"']/g, '').trim();
};

5. Excessive Console Warnings in Production

Location: Throughout attribution files

Many console.warn() calls will log to production:

console.warn('Attribution captured on web:', formatAttributionForLogging(captured)); // Line 94
console.warn('✅ Referral code saved to signup flow:', storedReferralCode); // Line 138

Impact: Low-Medium - Performance impact, cluttered logs, potential info disclosure
Recommendation: Use a logging abstraction with environment-aware levels

6. Hard-coded Attribution Window

Location: lib/attribution.ts:264-275, store/useAttributionStore.ts:431-439

30-day default may not suit all campaigns:

export const isAttributionExpired = (attributionData: Partial<AttributionData>, windowDays: number = 30): boolean

Recommendation: Make configurable per campaign or source

7. Missing Tests

Location: No test files found for attribution code

Critical business logic without test coverage:

  • Attribution parsing
  • Multi-touch attribution merging
  • PII sanitization
  • Channel categorization

Recommendation: Add unit tests for:

  • parseAttributionFromURL() with malicious inputs
  • sanitizeAttribution() with various PII patterns
  • getAttributionChannel() for all channel types
  • Store hydration race conditions

💡 Suggestions for Improvement

8. Performance: Unnecessary Store Reads

Location: lib/analytics.ts:209-210, 347:362-363

Store accessed on every event:

const attributionStore = useAttributionStore.getState(); // Called for EVERY event
const attributionData = attributionStore.getAttributionForEvent();

Recommendation: Cache attribution data with TTL or memoize

9. Code Duplication

Location: lib/attribution.ts and store/useAttributionStore.ts

URL parsing logic duplicated:

  • lib/attribution.ts:13-73 (parseAttributionFromURL)
  • store/useAttributionStore.ts:90-134 (parseAttributionFromURL)

Recommendation: Keep single source of truth in lib/attribution.ts

10. Incomplete Type Safety

Location: store/useAttributionStore.ts:175

Unsafe type casting:

sanitized[key as keyof AttributionData] = value.trim() as any; // 'as any' defeats type safety

Recommendation: Use proper type guards or discriminated unions

11. Missing Analytics Validation

Location: lib/analytics.ts:195-242

No validation that events conform to analytics schema:

export const track = (event: string, params: Record<string, any> = {}) => {
  // No validation that 'event' is a valid tracking event
  // No validation of param structure
}

Recommendation:

  • Validate against TRACKING_EVENTS enum
  • Use Zod schema for param validation
  • Provide TypeScript autocomplete for event names

12. Deep Link Listener Cleanup

Location: hooks/useAttributionInitialization.ts:124-134

Listener added but could leak if component unmounts during initialization:

subscriptionRef.current = Linking.addEventListener('url', event => {...});

Recommendation: Add cleanup in finally block or use AbortController pattern


📊 Test Coverage Recommendations

Create tests for:

  1. Unit Tests (lib/attribution.test.ts):

    • URL parsing with various formats
    • PII detection (all patterns)
    • Attribution channel categorization
    • Attribution expiry calculations
  2. Integration Tests (hooks/useAttribution.test.ts):

    • Store hydration flow
    • Web vs mobile initialization
    • First-touch vs last-touch merging
  3. E2E Tests:

    • Signup with referral code
    • Deposit with attribution tracking
    • Multi-session attribution persistence

🔒 Security Checklist

  • Expand PII detection patterns
  • Add input validation for all UTM parameters
  • Implement length limits on stored values
  • Audit console.warn() for sensitive data leaks
  • Add CSP headers for attribution endpoints
  • Rate limit attribution capture to prevent abuse

📈 Performance Considerations

  1. Store Access: Cache attribution data instead of reading on every event
  2. Promise.allSettled: Consider sequential execution for high-priority providers
  3. Deep Link Polling: 5-second polling interval may be aggressive - consider exponential backoff
  4. MMKV Storage: No apparent size limits - could grow unbounded with long sessions

🎨 Code Quality Suggestions

Naming Consistency

  • _hasHydrated vs _referralHasHydrated - inconsistent prefix usage
  • utm_source vs utmSource - mixing snake_case and camelCase

Documentation

  • Missing JSDoc for critical functions like mergeAttribution()
  • No examples of deep link format expectations
  • Attribution window behavior not documented

Error Messages

Make error messages more actionable:

// Current
console.error('Failed to capture attribution:', error);

// Better
console.error('Failed to capture attribution from URL', { url, error: error.message, userId });

📝 Summary

This is a solid foundation for attribution tracking! The architecture is well thought out and handles the complex cross-platform requirements effectively.

Priority Actions:

  1. 🔴 High: Fix PII detection and input validation (security)
  2. 🟡 Medium: Add error tracking and monitoring
  3. 🟡 Medium: Add comprehensive test coverage
  4. 🟢 Low: Performance optimizations and code cleanup

Estimated Risk Level: Medium

The core logic is sound, but security and error handling improvements are needed before production.

Great work on this implementation! 🚀

@LiorAgnin
LiorAgnin merged commit e1b99cd into qa Jan 7, 2026
5 checks passed
LiorAgnin added a commit that referenced this pull request Jan 7, 2026
Implement attribution tracking and initialization across the application
LiorAgnin added a commit that referenced this pull request Jan 7, 2026
…o-master

Merge pull request #1164 from Solid-Money/fix/analytics-attribution
@LiorAgnin
LiorAgnin deleted the fix/analytics-attribution branch January 7, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant